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 JavaArgumentConverters()57 private JavaArgumentConverters() { 58 } 59 getConverter(final Class<?> targetType)60 static MethodHandle getConverter(final Class<?> targetType) { 61 return CONVERTERS.get(targetType); 62 } 63 64 @SuppressWarnings("unused") toBoolean(final Object obj)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 toChar(final Object o)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 toCharPrimitive(final Object obj0)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". toString(final Object obj)134 static String toString(final Object obj) { 135 return obj == null ? null : JSType.toString(obj); 136 } 137 138 @SuppressWarnings("unused") toDouble(final Object obj0)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") toNumber(final Object obj0)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 toLong(final Object obj0)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 assertUnexpectedType(final Object obj)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") toLongPrimitive(final Object obj0)230 private static long toLongPrimitive(final Object obj0) { 231 final Long l = toLong(obj0); 232 return l == null ? 0L : l; 233 } 234 findOwnMH(final String name, final Class<?> rtype, final Class<?>... types)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 { CONVERTERS.put(Number.class, TO_NUMBER)242 CONVERTERS.put(Number.class, TO_NUMBER); CONVERTERS.put(String.class, TO_STRING)243 CONVERTERS.put(String.class, TO_STRING); 244 CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle())245 CONVERTERS.put(boolean.class, JSType.TO_BOOLEAN.methodHandle()); CONVERTERS.put(Boolean.class, TO_BOOLEAN)246 CONVERTERS.put(Boolean.class, TO_BOOLEAN); 247 CONVERTERS.put(char.class, TO_CHAR_PRIMITIVE)248 CONVERTERS.put(char.class, TO_CHAR_PRIMITIVE); CONVERTERS.put(Character.class, TO_CHAR)249 CONVERTERS.put(Character.class, TO_CHAR); 250 CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle())251 CONVERTERS.put(double.class, JSType.TO_NUMBER.methodHandle()); CONVERTERS.put(Double.class, TO_DOUBLE)252 CONVERTERS.put(Double.class, TO_DOUBLE); 253 CONVERTERS.put(long.class, TO_LONG_PRIMITIVE)254 CONVERTERS.put(long.class, TO_LONG_PRIMITIVE); CONVERTERS.put(Long.class, TO_LONG)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 putDoubleConverter(final Class<?> targetType)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 putLongConverter(final Class<?> targetType)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") byteValue(final Long l)277 private static Byte byteValue(final Long l) { 278 return l == null ? null : l.byteValue(); 279 } 280 281 @SuppressWarnings("unused") shortValue(final Long l)282 private static Short shortValue(final Long l) { 283 return l == null ? null : l.shortValue(); 284 } 285 286 @SuppressWarnings("unused") intValue(final Long l)287 private static Integer intValue(final Long l) { 288 return l == null ? null : l.intValue(); 289 } 290 291 @SuppressWarnings("unused") floatValue(final Double d)292 private static Float floatValue(final Double d) { 293 return d == null ? null : d.floatValue(); 294 } 295 296 } 297