/* * Kodkod -- Copyright (c) 2005-present, Emina Torlak * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package kodkod.test.util; import java.lang.reflect.Constructor; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.lang.reflect.Modifier; import java.util.ArrayList; import java.util.LinkedHashMap; import java.util.LinkedHashSet; import java.util.List; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; import java.util.regex.Pattern; import kodkod.ast.Formula; import kodkod.engine.fol2sat.TranslationLog; import kodkod.engine.satlab.ReductionStrategy; import kodkod.instance.Bounds; /** * Provides a collection of utility methods for finding and executing * benchmark methods and constructors reflectively. * * @author Emina Torlak */ public final class Reflection { private Reflection() {} /** * This method invokes each method in the given set on the given instance, * and returns a map from the invoked methods to the resulting objects. * @return a map from the given methods to the objects they produced when invoked * on the given instance. */ @SuppressWarnings("unchecked") public static <T> Map<Method,T> invokeAll(Object instance, Set<Method> methods) { final Map<Method, T> ret = new LinkedHashMap<Method, T>(); for(Method m : methods) { try { ret.put(m, (T)m.invoke(instance)); } catch (IllegalArgumentException e) { } catch (IllegalAccessException e) { } catch (InvocationTargetException e) { } } return ret; } /** * Returns all public member methods declared by the given class that take no arguments, * return a Formula, and whose name starts with the word "check". * @return all public member methods declared by the given class that take no arguments, * return a Formula, and whose name starts with the word "check". */ public static Set<Method> checks(Class<?> c) { final Set<Method> methods = new LinkedHashSet<Method>(); for(Method m : c.getMethods()) { if (m.getDeclaringClass().equals(c) && m.getName().startsWith("check") && noArgs(m) && returnsFormula(m)) { methods.add(m); } } return methods; } /** * Returns the public member method with the given name and no arguments that returns a formula. * @return public member method with the given name and no arguments that returns a formula. **/ public static Method formulaCreator(Class<?> c, String name) { try { Method m = c.getMethod(name, new Class[0]); if (returnsFormula(m)) return m; else { throw new IllegalArgumentException("Wrong signature for method " + name + "."); } } catch (SecurityException e) { throw new IllegalArgumentException("Cannot access method " + name + "."); } catch (NoSuchMethodException e) { throw new IllegalArgumentException("Method " + name + " does not exist."); } } /** * Returns the bounds for the given instance. This method assumes that the instance has a no-argument * method called "bounds" that returns a Bounds object. * @requires instance must have a method called bounds that takes no arguments and returns a Bounds object * @return instance.bounds() */ public static Bounds bounds(Object instance) { try { final Method bounder = instance.getClass().getMethod("bounds", new Class[]{}); return (Bounds) bounder.invoke(instance); } catch (SecurityException e) { throw new IllegalArgumentException(instance.getClass().getName() + " has no accessible Bounds bounds(int) method."); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(instance.getClass().getName() + " has no Bounds bounds(int) method."); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Could not invoke Bounds bounds(int) method of " + instance.getClass().getName() + "."); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Could not invoke Bounds bounds(int) method of " + instance.getClass().getName() + "."); } } /** * Returns the bounds for the given instance. This method assumes that the instance has a method called "bounds" that * takes a single integer argument and returns a Bounds object. * @requires instance must have a method called bounds that takes an integer argument and returns a Bounds object * @return instance.bounds(scope) */ public static Bounds bounds(Object instance, int scope) { try { final Method bounder = instance.getClass().getMethod("bounds", new Class[]{int.class}); return (Bounds) bounder.invoke(instance, scope); } catch (SecurityException e) { throw new IllegalArgumentException(instance.getClass().getName() + " has no accessible Bounds bounds(int) method."); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(instance.getClass().getName() + " has no Bounds bounds(int) method."); } catch (IllegalAccessException e) { throw new IllegalArgumentException("Could not invoke Bounds bounds(int) method of " + instance.getClass().getName() + "."); } catch (InvocationTargetException e) { throw new IllegalArgumentException("Could not invoke Bounds bounds(int) method of " + instance.getClass().getName() + "."); } } /** * Returns an instance of the given reduction strategy. * @requires strategy has a constructor that takes a translation log * @return an instance of the given reduction strategy. */ public static ReductionStrategy strategy(Class<? extends ReductionStrategy> strategy, TranslationLog log) { try { return strategy.getConstructor(TranslationLog.class).newInstance(log); } catch (IllegalArgumentException e) { throw e; } catch (SecurityException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible one-argument constructor."); } catch (InstantiationException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible one-argument constructor."); } catch (IllegalAccessException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible one-argument constructor."); } catch (InvocationTargetException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible one-argument constructor."); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible one-argument constructor."); } } /** * Returns an instance of the given reduction strategy. * @requires strategy has a constructor that takes a translation log and an integer * @return an instance of the given reduction strategy. */ public static ReductionStrategy strategy(Class<? extends ReductionStrategy> strategy, TranslationLog log, int depth) { try { return strategy.getConstructor(TranslationLog.class, int.class).newInstance(log, depth); } catch (IllegalArgumentException e) { throw e; } catch (SecurityException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible two-argument constructor."); } catch (InstantiationException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible two-argument constructor."); } catch (IllegalAccessException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible two-argument constructor."); } catch (InvocationTargetException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible two-argument constructor."); } catch (NoSuchMethodException e) { throw new IllegalArgumentException(strategy.getName() + " has no accessible two-argument constructor."); } } /** * Returns true if m takes no arguments. * @return true if m takes no arguments **/ public static boolean noArgs(Method m) { return m.getParameterTypes().length==0; } /** * Returns true if m returns a formula. * @return true if m returns a formula **/ public static boolean returnsFormula(Method m) { return Formula.class.isAssignableFrom(m.getReturnType()); } //-----CREATION FROM STRINGS-----// private static Matcher name = Pattern.compile("(.+?)\\(").matcher(""); private static Matcher arg = Pattern.compile("[\\(,]\\s*(.+?)\\s*[\\),]").matcher(""); /** * Returns the class with the given name * @throws IllegalArgumentException no such class exists * @return class with the given name */ public static Class<?> findClass(String className) { try { return Class.forName(className); } catch (ClassNotFoundException e) { throw new IllegalArgumentException(e); } } /** * Returns a fresh instance of the given class. * @requires the class has a no-argument constructor * @return a fresh instance of the given class */ public static Object instance(Class<?> c) { try { return c.newInstance(); } catch (InstantiationException e) { throw new IllegalArgumentException(c.getName() + " has no accessible nullary constructor."); } catch (IllegalAccessException e) { throw new IllegalArgumentException(c.getName() + " has no accessible nullary constructor."); } } /** * Returns all arguments in the given call, in the order in which they appear. * @return all arguments in the given call, in the order in which they appear. */ private static String[] args(String call) { final List<String> args = new ArrayList<String>(); arg.reset(call); if (arg.find()) { args.add(arg.group(1)); while(arg.find(arg.end()-1)) { args.add(arg.group(1)); } } return args.toArray(new String[args.size()]); } /** * Converts the given argument, represented as a string, to the given type. * @throws RuntimeException the conversion is not possible. * @return the given argument converted to the given type */ @SuppressWarnings({ "unchecked", "rawtypes" }) private static Object convert(String arg, Class<?> type) { try { if (type.isPrimitive()) { if (type==int.class) { return new Integer(arg); } else if (type==long.class) { return new Long(arg); } else if (type==boolean.class) { return new Boolean(arg); } else if (type==double.class) { return new Double(arg); } else if (type==float.class) { return new Float(arg); } else if (type==byte.class) { return new Byte(arg); } else if (type==short.class) { return new Short(arg); } else if (type==char.class && arg.length()==1) { return new Character(arg.charAt(0)); } else { throw new IllegalArgumentException("Unknown primitive type: " + type); } } else if (type==String.class) { return arg; } else if (type.isEnum()) { return Enum.valueOf((Class)type, arg); } else if (Number.class.isAssignableFrom(type) || type==Boolean.class) { final Constructor<?> c = type.getConstructor(String.class); return c.newInstance(arg); } else if (type==Character.class && arg.length()==1) { return new Character(arg.charAt(0)); } } catch (NoSuchMethodException e) { } catch (IllegalAccessException e) { } catch (InstantiationException e) { } catch (InvocationTargetException e) { } throw new IllegalArgumentException(); } /** * Converts the given arguments, represented as strings, to appropriate types, if possible. * If not returns null. * @requires args.length = argTypes.length * @return an array containing the given arguments, converted to appropriate types, if possible; * null if not. */ private static Object[] convert(String[] args, Class<?>[] argTypes) { assert args.length==argTypes.length; final Object[] out = new Object[args.length]; for(int i = 0; i < args.length; i++) { try { out[i] = convert(args[i], argTypes[i]); } catch(RuntimeException e) { return null; } } return out; } /** * Reflectively performs the given invocation and returns the result. * @requires call is of the form "name(arg1, arg2, ..., argn)" where name is the * full class name of the class whose constructor is to be invoked (e.g. java.lang.Integer) and * the arguments are either strings, primitives, objects corresponding to primitives (Integer, Long, etc), * or publicly accessible Enums. * @throws IllegalArgumentException call is not formatted as specified above, or it could not be made for some reason. * @return an object of type T, created reflectively from the given call String */ @SuppressWarnings("unchecked") public static <T> T construct(String call) { try { name.reset(call); if (!name.find()) throw new IllegalArgumentException("Badly formatted call: " + call); final Class<?> callClass = findClass(name.group(1)); final String[] args = args(call); for(Constructor<?> c : callClass.getConstructors()) { final Class<?>[] argTypes = c.getParameterTypes(); if (argTypes.length==args.length) { final Object[] convertedArgs = convert(args, argTypes); if (convertedArgs!=null) { return (T) c.newInstance(convertedArgs); } } } throw new IllegalAccessException("Could not call " + call); } catch (InstantiationException e) { throw new IllegalArgumentException(e); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } catch (InvocationTargetException e) { throw new IllegalArgumentException(e); } } /** * Reflectively performs the specified invocation and returns the result. * @requires call is of the form "name(arg1, arg2, ..., argn)" where name is the * full name of the static method to be invoked (e.g. java.lang.Integer#parseInt) and * the arguments are either strings, primitives, objects corresponding to primitives (Integer, Long, etc), * or publicly accessible Enums. * @throws IllegalArgumentException call is not formatted as specified above, or it could not be made for some reason. * @return an object of type T, created reflectively from executing the given call String */ @SuppressWarnings("unchecked") public static <T> T create(String call) { try { name.reset(call); if (!name.find()) throw new IllegalArgumentException("Badly formatted call: " + call); final String[] parts = name.group(1).split("#"); if (parts.length!=2) throw new IllegalArgumentException("Badly formatted call: " + call); final Class<?> callClass = findClass(parts[0]); final String[] args = args(call); for(Method m : callClass.getDeclaredMethods()) { final int mod = m.getModifiers(); if (!(parts[1].equals(m.getName()) && Modifier.isPublic(mod) && Modifier.isStatic(mod))) continue; final Class<?>[] argTypes = m.getParameterTypes(); if (argTypes.length==args.length) { final Object[] convertedArgs = convert(args, argTypes); if (convertedArgs!=null) { return (T) m.invoke(null, convertedArgs); } } } throw new IllegalAccessException("Could not call " + call); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } catch (InvocationTargetException e) { throw new IllegalArgumentException(e); } } /** * Reflectively performs the specified invocation on the specified instance and returns the result. * @requires call is of the form "name(arg1, arg2, ..., argn)" where name is the * name of the method to be invoked on the specified object and * the arguments are either strings, primitives, objects corresponding to primitives (Integer, Long, etc), * or publicly accessible Enums. * @throws IllegalArgumentException call is not formatted as specified above, or it could not be made for some reason. * @return an object of type T, created reflectively from executing the given call String on the given instance */ @SuppressWarnings("unchecked") public static <T> T create(Object instance, String call) { try { name.reset(call); if (!name.find()) throw new IllegalArgumentException("Badly formatted call: " + call); final String method = name.group(1); final Class<?> callClass = instance.getClass(); final String[] args = args(call); for(Method m : callClass.getMethods()) { if (!method.equals(m.getName())) continue; final Class<?>[] argTypes = m.getParameterTypes(); if (argTypes.length==args.length) { final Object[] convertedArgs = convert(args, argTypes); if (convertedArgs!=null) { return (T) m.invoke(instance, convertedArgs); } } } throw new IllegalAccessException("Could not call " + call); } catch (IllegalAccessException e) { throw new IllegalArgumentException(e); } catch (InvocationTargetException e) { throw new IllegalArgumentException(e); } } }