/* * Written by Gil Tene and Martin Thompson, and released to the public domain, * as explained at http://creativecommons.org/publicdomain/zero/1.0/ */ package org.ObjectLayout; import java.lang.invoke.MethodHandles; import java.lang.reflect.Constructor; /** * Captures a specific constructor and arguments to be passed to it. Useful for providing * fully customizable, per-element construction behavior for iterative constructors (such as those * used for elements in {@link StructuredArray}) * * @param <T> type of the element occupying each array slot. */ public class CtorAndArgs<T> { private static final Class[] EMPTY_ARG_TYPES = new Class[0]; private static final Object[] EMPTY_ARGS = new Object[0]; private static final MethodHandles.Lookup noLookup = null; private Constructor<T> constructor; private Object[] args; private Object contextCookie; /** * Create a {@link CtorAndArgs} instance. The presumption is that types in args * match those expected by constructor. Obviously exceptions may be generated at construction time if * this is not the case. * * @param constructor Constructor to be indicated in this {@link CtorAndArgs} * @param args constructor arguments to be indicated in this {@link CtorAndArgs} */ public CtorAndArgs(final Constructor<T> constructor, final Object... args) { setConstructor(constructor); setArgs(args); } /** * Create a {@link CtorAndArgs} instance for the given instanceClass, with a constructor * identified by the constructorArgTypes, and using the args provides. The presumption is * that types in args match those expected by constructor. Obviously exceptions may be * generated at construction time if this is not the case. * * @param instanceClass The class which the constructor in this {@link CtorAndArgs} will instantiate * @param constructorArgTypes The argument types expected by the constructor * @param args constructor arguments to be indicated in this {@link CtorAndArgs} */ public CtorAndArgs( final Class<T> instanceClass, final Class[] constructorArgTypes, final Object... args) { this(noLookup, instanceClass, constructorArgTypes, args); } /** * Create a {@link CtorAndArgs} instance for the given instanceClass, with a constructor * identified by the constructorArgTypes, and using the args provides. The presumption is * that types in args match those expected by constructor. Obviously exceptions may be * generated at construction time if this is not the case. * * @param lookup The lookup object to use when resolving constructors * @param instanceClass The class which the constructor in this {@link CtorAndArgs} will instantiate * @param constructorArgTypes The argument types expected by the constructor * @param args constructor arguments to be indicated in this {@link CtorAndArgs} */ public CtorAndArgs( MethodHandles.Lookup lookup, final Class<T> instanceClass, final Class[] constructorArgTypes, final Object... args) { try { if (constructorArgTypes.length != args.length) { throw new IllegalArgumentException("argument types and values must be the same length"); } @SuppressWarnings("unchecked") Constructor<T> ctor = instanceClass.getDeclaredConstructor(constructorArgTypes); if (lookup != null) { if (!belongsToThisPackage(ctor.getDeclaringClass())) { lookup.unreflectConstructor(ctor); // May throw IllegalAccessException. // The fact the the provided lookup Proves we may setAccessible. ctor.setAccessible(true); } } setConstructor(ctor); setArgs(args); } catch (NoSuchMethodException | IllegalAccessException e) { throw new RuntimeException(e); } } /** * Create a {@link CtorAndArgs} instance for the given instanceClass, using the default constructor (and no args) * * @param instanceClass The class which the constructor in this {@link CtorAndArgs} will instantiate */ public CtorAndArgs(final Class<T> instanceClass) { this(instanceClass, EMPTY_ARG_TYPES, EMPTY_ARGS); } /** * Create a {@link CtorAndArgs} instance for the given instanceClass, using the default constructor (and no args) * * @param lookup The lookup object to use when resolving constructors * @param instanceClass The class which the constructor in this {@link CtorAndArgs} will instantiate */ public CtorAndArgs( MethodHandles.Lookup lookup, final Class<T> instanceClass) { this(lookup, instanceClass, EMPTY_ARG_TYPES, EMPTY_ARGS); } /** * @return the Constructor indicated in this CtorAndArgs */ public final Constructor<T> getConstructor() { return constructor; } /** * Set the constructor to be indicated in this {@link CtorAndArgs}. Enables recycling of * {@link CtorAndArgs} objects to avoid re-allocation. E.g. in copy construction loops. * * @param constructor Constructor to be indicated in this CtorAndArgs * @return the {@link CtorAndArgs} instance operated on */ public final CtorAndArgs<T> setConstructor(final Constructor<T> constructor) { if (null == constructor) { throw new NullPointerException("constructor cannot be null"); } this.constructor = constructor; return this; } /** * @return the constructor arguments indicated in this CtorAndArgs */ public final Object[] getArgs() { return args; } /** * Set the constructor arguments to be indicated in this CtorAndArgs. Enables recycling of * {@link CtorAndArgs} objects to avoid re-allocation. E.g. in copy construction loops. * * @param args constructor arguments to be indicated in this {@link CtorAndArgs} * @return the {@link CtorAndArgs} instance operated on */ public final CtorAndArgs<T> setArgs(final Object... args) { this.args = args; return this; } /** * Get the value of the opaque contextCookie object * @return opaque contextCookie object (may be null) */ public Object getContextCookie() { return contextCookie; } /** * Set the value of the opaque contextCookie object * @param contextCookie carries an opaque object */ public void setContextCookie(final Object contextCookie) { this.contextCookie = contextCookie; } private static final ClassLoader thisClassLoader = CtorAndArgs.class.getClassLoader(); private static final int indexOfLastPeriodInThisPackageName = CtorAndArgs.class.getName().lastIndexOf('.'); private static final String thisPackageName = CtorAndArgs.class.getName().substring(0, indexOfLastPeriodInThisPackageName); static boolean belongsToThisPackage(Class<?> cls) { if (cls.getClassLoader() != thisClassLoader) { return false; } if (cls.isArray()) { return false; // Arrays are not in this class. } String className = cls.getName(); // See if length of package name differs: if (className.lastIndexOf('.') != indexOfLastPeriodInThisPackageName) { return false; } return className.startsWith(thisPackageName); } }