/* * Copyright (c) 2014, 2017, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package test.java.lang.invoke.lib; import jdk.testlibrary.Asserts; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodType; import java.lang.reflect.Array; import java.util.ArrayList; import java.util.Arrays; import java.util.List; import java.util.Random; public class Helper { /** Flag for verbose output, true if {@code -Dverbose} specified */ public static final boolean IS_VERBOSE = System.getProperty("verbose") != null; /** * Flag for thorough testing -- all test will be executed, * true if {@code -Dthorough} specified. */ public static final boolean IS_THOROUGH = System.getProperty("thorough") != null; /** Random number generator w/ initial seed equal to {@code -Dseed} */ public static final Random RNG; static { String str = System.getProperty("seed"); long seed = str != null ? Long.parseLong(str) : new Random().nextLong(); RNG = new Random(seed); System.out.printf("-Dseed=%d%n", seed); } public static final long TEST_LIMIT; static { String str = System.getProperty("testLimit"); TEST_LIMIT = str != null ? Long.parseUnsignedLong(str) : 2000L; System.out.printf("-DtestLimit=%d%n", TEST_LIMIT); } public static final int MAX_ARITY = 254; public static final String MISSING_ARG = "missingArg"; public static final String MISSING_ARG_2 = "missingArg#2"; private static final int // first int value ONE_MILLION = (1000 * 1000), // scale factor to reach upper 32 bits TEN_BILLION = (10 * 1000 * 1000 * 1000), // <<1 makes space for sign bit; INITIAL_ARG_VAL = ONE_MILLION << 1; public static final MethodHandle AS_LIST; static { try { AS_LIST = MethodHandles.lookup().findStatic( Arrays.class, "asList", MethodType.methodType(List.class, Object[].class)); } catch (NoSuchMethodException | IllegalAccessException ex) { throw new Error(ex); } } public static boolean isDoubleCost(Class aClass) { return aClass == double.class || aClass == long.class; } private static List> calledLog = new ArrayList<>(); private static long nextArgVal; public static void assertCalled(String name, Object... args) { assertCalled(0, name, args); } public static void assertCalled(int lag, String name, Object... args) { Object expected = logEntry(name, args); Object actual = getCalled(lag); Asserts.assertEQ(expected, actual, "method call w/ lag = " + lag); } public static Object called(String name, Object... args) { List entry = logEntry(name, args); calledLog.add(entry); return entry; } private static List logEntry(String name, Object... args) { return Arrays.asList(name, Arrays.asList(args)); } public static void clear() { calledLog.clear(); } public static List getCalled(int lag) { int size = calledLog.size(); return size <= lag ? null : calledLog.get(size - lag - 1); } public static List> randomClasses(Class[] classes, int size) { List> result = new ArrayList<>(size); for (int i = 0; i < size; ++i) { result.add(classes[RNG.nextInt(classes.length)]); } return result; } public static List> getParams(List> classes, boolean isVararg, int argsCount) { boolean unmodifiable = true; List> result = classes.subList(0, Math.min(argsCount, (MAX_ARITY / 2) - 1)); int extra = 0; if (argsCount >= MAX_ARITY / 2) { result = new ArrayList<>(result); unmodifiable = false; extra = (int) result.stream().filter(Helper::isDoubleCost).count(); int i = result.size(); while (result.size() + extra < argsCount) { Class aClass = classes.get(i); if (Helper.isDoubleCost(aClass)) { ++extra; if (result.size() + extra >= argsCount) { break; } } result.add(aClass); } } if (isVararg && result.size() > 0) { if (unmodifiable) { result = new ArrayList<>(result); } int last = result.size() - 1; Class aClass = result.get(last); aClass = Array.newInstance(aClass, 2).getClass(); result.set(last, aClass); } return result; } public static MethodHandle addTrailingArgs(MethodHandle target, int nargs, List> classes) { int targetLen = target.type().parameterCount(); int extra = (nargs - targetLen); if (extra <= 0) { return target; } List> fakeArgs = new ArrayList<>(extra); for (int i = 0; i < extra; ++i) { fakeArgs.add(classes.get(i % classes.size())); } return MethodHandles.dropArguments(target, targetLen, fakeArgs); } public static MethodHandle varargsList(int arity) { return AS_LIST.asCollector(Object[].class, arity); } private static long nextArg(boolean moreBits) { long val = nextArgVal++; long sign = -(val & 1); // alternate signs val >>= 1; if (moreBits) // Guarantee some bits in the high word. // In any case keep the decimal representation simple-looking, // with lots of zeroes, so as not to make the printed decimal // strings unnecessarily noisy. { val += (val % ONE_MILLION) * TEN_BILLION; } return val ^ sign; } private static int nextArg() { // Produce a 32-bit result something like ONE_MILLION+(smallint). // Example: 1_000_042. return (int) nextArg(false); } private static long nextArg(Class kind) { if (kind == long.class || kind == Long.class || kind == double.class || kind == Double.class) // produce a 64-bit result something like // ((TEN_BILLION+1) * (ONE_MILLION+(smallint))) // Example: 10_000_420_001_000_042. { return nextArg(true); } return (long) nextArg(); } private static Object randomArg(Class param) { Object wrap = castToWrapperOrNull(nextArg(param), param); if (wrap != null) { return wrap; } if (param.isInterface()) { for (Class c : param.getClasses()) { if (param.isAssignableFrom(c) && !c.isInterface()) { param = c; break; } } } if (param.isArray()) { Class ctype = param.getComponentType(); Object arg = Array.newInstance(ctype, 2); Array.set(arg, 0, randomArg(ctype)); return arg; } if (param.isInterface() && param.isAssignableFrom(List.class)) { return Arrays.asList("#" + nextArg()); } if (param.isInterface() || param.isAssignableFrom(String.class)) { return "#" + nextArg(); } try { return param.newInstance(); } catch (InstantiationException | IllegalAccessException ex) { } return null; // random class not Object, String, Integer, etc. } public static Object[] randomArgs(Class... params) { Object[] args = new Object[params.length]; for (int i = 0; i < args.length; i++) { args[i] = randomArg(params[i]); } return args; } public static Object[] randomArgs(int nargs, Class param) { Object[] args = new Object[nargs]; for (int i = 0; i < args.length; i++) { args[i] = randomArg(param); } return args; } public static Object[] randomArgs(int nargs, Class... params) { Object[] args = new Object[nargs]; for (int i = 0; i < args.length; i++) { Class param = params[i % params.length]; args[i] = randomArg(param); } return args; } public static Object[] randomArgs(List> params) { return randomArgs(params.toArray(new Class[params.size()])); } public static Object castToWrapper(Object value, Class dst) { Object wrap = null; if (value instanceof Number) { wrap = castToWrapperOrNull(((Number) value).longValue(), dst); } if (value instanceof Character) { wrap = castToWrapperOrNull((char) (Character) value, dst); } if (wrap != null) { return wrap; } return dst.cast(value); } @SuppressWarnings("cast") // primitive cast to (long) is part of the pattern private static Object castToWrapperOrNull(long value, Class dst) { if (dst == int.class || dst == Integer.class) { return (int) (value); } if (dst == long.class || dst == Long.class) { return (long) (value); } if (dst == char.class || dst == Character.class) { return (char) (value); } if (dst == short.class || dst == Short.class) { return (short) (value); } if (dst == float.class || dst == Float.class) { return (float) (value); } if (dst == double.class || dst == Double.class) { return (double) (value); } if (dst == byte.class || dst == Byte.class) { return (byte) (value); } if (dst == boolean.class || dst == Boolean.class) { return ((value % 29) & 1) == 0; } return null; } /** * Routine used to obtain a randomly generated method type. * * @param arity Arity of returned method type. * @return MethodType generated randomly. */ public static MethodType randomMethodTypeGenerator(int arity) { final Class[] CLASSES = { Object.class, int.class, boolean.class, byte.class, short.class, char.class, long.class, float.class, double.class }; if (arity > MAX_ARITY) { throw new IllegalArgumentException( String.format("Arity should not exceed %d!", MAX_ARITY)); } List> list = randomClasses(CLASSES, arity); list = getParams(list, false, arity); int i = RNG.nextInt(CLASSES.length + 1); Class rtype = i == CLASSES.length ? void.class : CLASSES[i]; return MethodType.methodType(rtype, list); } }