1 /*
   2  * Copyright (c) 2010, 2013, Oracle and/or its affiliates. All rights reserved.
   3  * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
   4  *
   5  * This code is free software; you can redistribute it and/or modify it
   6  * under the terms of the GNU General Public License version 2 only, as
   7  * published by the Free Software Foundation.  Oracle designates this
   8  * particular file as subject to the "Classpath" exception as provided
   9  * by Oracle in the LICENSE file that accompanied this code.
  10  *
  11  * This code is distributed in the hope that it will be useful, but WITHOUT
  12  * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
  13  * FITNESS FOR A PARTICULAR PURPOSE.  See the GNU General Public License
  14  * version 2 for more details (a copy is included in the LICENSE file that
  15  * accompanied this code).
  16  *
  17  * You should have received a copy of the GNU General Public License version
  18  * 2 along with this work; if not, write to the Free Software Foundation,
  19  * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
  20  *
  21  * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
  22  * or visit www.oracle.com if you need additional information or have any
  23  * questions.
  24  */
  25 
  26 package jdk.nashorn.internal.runtime.linker;
  27 
  28 import static jdk.nashorn.internal.lookup.Lookup.MH;
  29 import static jdk.nashorn.internal.runtime.ECMAErrors.typeError;
  30 import static jdk.nashorn.internal.runtime.JSType.isString;
  31 import static jdk.nashorn.internal.runtime.ScriptRuntime.UNDEFINED;
  32 
  33 import java.lang.invoke.MethodHandle;
  34 import java.lang.invoke.MethodHandles;
  35 import java.util.HashMap;
  36 import java.util.Map;
  37 import jdk.dynalink.linker.support.TypeUtilities;
  38 import jdk.nashorn.internal.runtime.ConsString;
  39 import jdk.nashorn.internal.runtime.JSType;
  40 import jdk.nashorn.internal.runtime.ScriptObject;
  41 
  42 /**
  43  * Utility class shared by {@code NashornLinker} and {@code NashornPrimitiveLinker} for converting JS values to Java
  44  * types.
  45  */
  46 final class JavaArgumentConverters {
  47 
  48     private static final MethodHandle TO_BOOLEAN        = findOwnMH("toBoolean", Boolean.class, Object.class);
  49     private static final MethodHandle TO_STRING         = findOwnMH("toString", String.class, Object.class);
  50     private static final MethodHandle TO_DOUBLE         = findOwnMH("toDouble", Double.class, Object.class);
  51     private static final MethodHandle TO_NUMBER         = findOwnMH("toNumber", Number.class, Object.class);
  52     private static final MethodHandle TO_LONG           = findOwnMH("toLong", Long.class, Object.class);
  53     private static final MethodHandle TO_LONG_PRIMITIVE = findOwnMH("toLongPrimitive", long.class, Object.class);
  54     private static final MethodHandle TO_CHAR           = findOwnMH("toChar", Character.class, Object.class);
  55     private static final MethodHandle TO_CHAR_PRIMITIVE = findOwnMH("toCharPrimitive", char.class, Object.class);
  56 
  57     private JavaArgumentConverters() {
  58     }
  59 
  60     static MethodHandle getConverter(final Class<?> targetType) {
  61         return CONVERTERS.get(targetType);
  62     }
  63 
  64     @SuppressWarnings("unused")
  65     private static Boolean toBoolean(final Object obj) {
  66         if (obj instanceof Boolean) {
  67             return (Boolean) obj;
  68         }
  69 
  70         if (obj == null) {
  71             // NOTE: FindBugs complains here about the NP_BOOLEAN_RETURN_NULL pattern: we're returning null from a
  72             // method that has a return type of Boolean, as it is worried about a NullPointerException if there's a
  73             // conversion to a primitive boolean. We know what we're doing, though. We're using a separate method when
  74             // we're converting Object to a primitive boolean - see how the CONVERTERS map is populated. We specifically
  75             // want to have null and Undefined to be converted to a (Boolean)null when being passed to a Java method
  76             // that expects a Boolean argument.
  77             // TODO: if/when we're allowed to use FindBugs at build time, we can use annotations to disable this warning
  78             return null;
  79         }
  80 
  81         if (obj == UNDEFINED) {
  82             // NOTE: same reasoning for FindBugs NP_BOOLEAN_RETURN_NULL warning as in the preceding comment.
  83             return null;
  84         }
  85 
  86         if (obj instanceof Number) {
  87             final double num = ((Number) obj).doubleValue();
  88             return num != 0 && !Double.isNaN(num);
  89         }
  90 
  91         if (isString(obj)) {
  92             return ((CharSequence) obj).length() > 0;
  93         }
  94 
  95         if (obj instanceof ScriptObject) {
  96             return true;
  97         }
  98 
  99         throw assertUnexpectedType(obj);
 100     }
 101 
 102     private static Character toChar(final Object o) {
 103         if (o == null) {
 104             return null;
 105         }
 106 
 107         if (o instanceof Number) {
 108             final int ival = ((Number)o).intValue();
 109             if (ival >= Character.MIN_VALUE && ival <= Character.MAX_VALUE) {
 110                 return (char) ival;
 111             }
 112 
 113             throw typeError("cant.convert.number.to.char");
 114         }
 115 
 116         final String s = toString(o);
 117         if (s == null) {
 118             return null;
 119         }
 120 
 121         if (s.length() != 1) {
 122             throw typeError("cant.convert.string.to.char");
 123         }
 124 
 125         return s.charAt(0);
 126     }
 127 
 128     static char toCharPrimitive(final Object obj0) {
 129         final Character c = toChar(obj0);
 130         return c == null ? (char)0 : c;
 131     }
 132 
 133     // Almost identical to ScriptRuntime.toString, but returns null for null instead of the string "null".
 134     static String toString(final Object obj) {
 135         return obj == null ? null : JSType.toString(obj);
 136     }
 137 
 138     @SuppressWarnings("unused")
 139     private static Double toDouble(final Object obj0) {
 140         // TODO - Order tests for performance.
 141         for (Object obj = obj0; ;) {
 142             if (obj == null) {
 143                 return null;
 144             } else if (obj instanceof Double) {
 145                 return (Double) obj;
 146             } else if (obj instanceof Number) {
 147                 return ((Number)obj).doubleValue();
 148             } else if (obj instanceof String) {
 149                 return JSType.toNumber((String) obj);
 150             } else if (obj instanceof ConsString) {
 151                 return JSType.toNumber(obj.toString());
 152             } else if (obj instanceof Boolean) {
 153                 return (Boolean) obj ? 1 : +0.0;
 154             } else if (obj instanceof ScriptObject) {
 155                 obj = JSType.toPrimitive(obj, Number.class);
 156                 continue;
 157             } else if (obj == UNDEFINED) {
 158                 return Double.NaN;
 159             }
 160             throw assertUnexpectedType(obj);
 161         }
 162     }
 163 
 164     @SuppressWarnings("unused")
 165     private static Number toNumber(final Object obj0) {
 166         // TODO - Order tests for performance.
 167         for (Object obj = obj0; ;) {
 168             if (obj == null) {
 169                 return null;
 170             } else if (obj instanceof Number) {
 171                 return (Number) obj;
 172             } else if (obj instanceof String) {
 173                 return JSType.toNumber((String) obj);
 174             } else if (obj instanceof ConsString) {
 175                 return JSType.toNumber(obj.toString());
 176             } else if (obj instanceof Boolean) {
 177                 return (Boolean) obj ? 1 : +0.0;
 178             } else if (obj instanceof ScriptObject) {
 179                 obj = JSType.toPrimitive(obj, Number.class);
 180                 continue;
 181             } else if (obj == UNDEFINED) {
 182                 return Double.NaN;
 183             }
 184             throw assertUnexpectedType(obj);
 185         }
 186     }
 187 
 188     private static Long toLong(final Object obj0) {
 189         // TODO - Order tests for performance.
 190         for (Object obj = obj0; ;) {
 191             if (obj == null) {
 192                 return null;
 193             } else if (obj instanceof Long) {
 194                 return (Long) obj;
 195             } else if (obj instanceof Integer) {
 196                 return ((Integer)obj).longValue();
 197             } else if (obj instanceof Double) {
 198                 final Double d = (Double)obj;
 199                 if(Double.isInfinite(d)) {
 200                     return 0L;
 201                 }
 202                 return d.longValue();
 203             } else if (obj instanceof Float) {
 204                 final Float f = (Float)obj;
 205                 if(Float.isInfinite(f)) {
 206                     return 0L;
 207                 }
 208                 return f.longValue();
 209             } else if (obj instanceof Number) {
 210                 return ((Number)obj).longValue();
 211             } else if (isString(obj)) {
 212                 return JSType.toLong(obj);
 213             } else if (obj instanceof Boolean) {
 214                 return (Boolean)obj ? 1L : 0L;
 215             } else if (obj instanceof ScriptObject) {
 216                 obj = JSType.toPrimitive(obj, Number.class);
 217                 continue;
 218             } else if (obj == UNDEFINED) {
 219                 return null; // null or 0L?
 220             }
 221             throw assertUnexpectedType(obj);
 222         }
 223     }
 224 
 225     private static AssertionError assertUnexpectedType(final Object obj) {
 226         return new AssertionError("Unexpected type" + obj.getClass().getName() + ". Guards should have prevented this");
 227     }
 228 
 229     @SuppressWarnings("unused")
 230     private static long toLongPrimitive(final Object obj0) {
 231         final Long l = toLong(obj0);
 232         return l == null ? 0L : l;
 233     }
 234 
 235     private static MethodHandle findOwnMH(final String name, final Class<?> rtype, final Class<?>... types) {
 236         return MH.findStatic(MethodHandles.lookup(), JavaArgumentConverters.class, name, MH.type(rtype, types));
 237     }
 238 
 239     private static final Map<Class<?>, MethodHandle> CONVERTERS = new HashMap<>();
 240 
 241     static {
 242         CONVERTERS.put(Number.class, TO_NUMBER);
 243         CONVERTERS.put(String.class, TO_STRING);
 244 
 245         CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle());
 246         CONVERTERS.put(Boolean.class, TO_BOOLEAN);
 247 
 248         CONVERTERS.put(char.class, TO_CHAR_PRIMITIVE);
 249         CONVERTERS.put(Character.class, TO_CHAR);
 250 
 251         CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle());
 252         CONVERTERS.put(Double.class, TO_DOUBLE);
 253 
 254         CONVERTERS.put(long.class, TO_LONG_PRIMITIVE);
 255         CONVERTERS.put(Long.class, TO_LONG);
 256 
 257         putLongConverter(Byte.class);
 258         putLongConverter(Short.class);
 259         putLongConverter(Integer.class);
 260         putDoubleConverter(Float.class);
 261 
 262     }
 263 
 264     private static void putDoubleConverter(final Class<?> targetType) {
 265         final Class<?> primitive = TypeUtilities.getPrimitiveType(targetType);
 266         CONVERTERS.put(primitive,  MH.explicitCastArguments(JSType.TO_NUMBER.methodHandle(), JSType.TO_NUMBER.methodHandle().type().changeReturnType(primitive)));
 267         CONVERTERS.put(targetType, MH.filterReturnValue(TO_DOUBLE, findOwnMH(primitive.getName() + "Value", targetType, Double.class)));
 268     }
 269 
 270     private static void putLongConverter(final Class<?> targetType) {
 271         final Class<?> primitive = TypeUtilities.getPrimitiveType(targetType);
 272         CONVERTERS.put(primitive,  MH.explicitCastArguments(TO_LONG_PRIMITIVE, TO_LONG_PRIMITIVE.type().changeReturnType(primitive)));
 273         CONVERTERS.put(targetType, MH.filterReturnValue(TO_LONG, findOwnMH(primitive.getName() + "Value", targetType, Long.class)));
 274     }
 275 
 276     @SuppressWarnings("unused")
 277     private static Byte byteValue(final Long l) {
 278         return l == null ? null : l.byteValue();
 279     }
 280 
 281     @SuppressWarnings("unused")
 282     private static Short shortValue(final Long l) {
 283         return l == null ? null : l.shortValue();
 284     }
 285 
 286     @SuppressWarnings("unused")
 287     private static Integer intValue(final Long l) {
 288         return l == null ? null : l.intValue();
 289     }
 290 
 291     @SuppressWarnings("unused")
 292     private static Float floatValue(final Double d) {
 293         return d == null ? null : d.floatValue();
 294     }
 295 
 296 }