package org.simpleflatmapper.reflect; import org.simpleflatmapper.reflect.asm.AsmFactory; import org.simpleflatmapper.reflect.getter.IdentityGetter; import org.simpleflatmapper.reflect.impl.BuilderBiInstantiator; import org.simpleflatmapper.reflect.impl.BuilderInstantiator; import org.simpleflatmapper.reflect.impl.EmptyConstructorBiInstantiator; import org.simpleflatmapper.reflect.impl.EmptyConstructorInstantiator; import org.simpleflatmapper.reflect.impl.EmptyStaticMethodBiInstantiator; import org.simpleflatmapper.reflect.impl.EmptyStaticMethodInstantiator; import org.simpleflatmapper.reflect.impl.InjectConstructorBiInstantiator; import org.simpleflatmapper.reflect.impl.InjectConstructorInstantiator; import org.simpleflatmapper.reflect.impl.InjectStaticMethodBiInstantiator; import org.simpleflatmapper.reflect.impl.InjectStaticMethodInstantiator; import org.simpleflatmapper.reflect.instantiator.ExecutableInstantiatorDefinition; import org.simpleflatmapper.reflect.instantiator.InstantiatorDefinitions; import org.simpleflatmapper.util.BiFunction; import org.simpleflatmapper.util.ErrorHelper; import java.lang.reflect.*; import java.util.ArrayList; import java.util.Arrays; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Set; public class InstantiatorFactory { private final AsmFactory asmFactory; private final boolean failOnAsmError; public InstantiatorFactory(final AsmFactory asmFactory) { this(asmFactory, false); } public InstantiatorFactory(AsmFactory asmFactory, boolean faileOnAsmError) { this.asmFactory = asmFactory; this.failOnAsmError = faileOnAsmError; } @SuppressWarnings("unchecked") public <S1, S2, T> BiInstantiator<S1, S2, T> getBiInstantiator(Type target, final Class<?> s1, final Class<?> s2, List<InstantiatorDefinition> constructors, Map<org.simpleflatmapper.reflect.Parameter, BiFunction<? super S1, ? super S2, ?>> injections, boolean useAsmIfEnabled) throws SecurityException { final InstantiatorDefinition instantiatorDefinition = getSmallerConstructor(constructors); if (instantiatorDefinition == null) { throw new IllegalArgumentException("No constructor available for " + target); } return this.getBiInstantiator(instantiatorDefinition, s1, s2, injections, useAsmIfEnabled); } @SuppressWarnings("unchecked") public <S1, S2, T> BiInstantiator<S1, S2, T> getBiInstantiator(InstantiatorDefinition instantiatorDefinition, Class<?> s1, Class<?> s2, Map<org.simpleflatmapper.reflect.Parameter, BiFunction<? super S1, ? super S2, ?>> injections, boolean useAsmIfEnabled) { checkParameters(instantiatorDefinition, injections.keySet()); if (asmFactory != null && useAsmIfEnabled) { if (instantiatorDefinition instanceof ExecutableInstantiatorDefinition) { ExecutableInstantiatorDefinition executableInstantiatorDefinition = (ExecutableInstantiatorDefinition) instantiatorDefinition; Member executable = executableInstantiatorDefinition.getExecutable(); if (Modifier.isPublic(executable.getModifiers())) { try { return asmFactory.createBiInstantiator(s1, s2, executableInstantiatorDefinition, injections); } catch (Exception e) { // fall back on reflection if (failOnAsmError) ErrorHelper.rethrow(e); } } } else { try { return asmFactory.createBiInstantiator(s1, s2, (BuilderInstantiatorDefinition)instantiatorDefinition, injections); } catch (Exception e) { // fall back on reflection if (failOnAsmError) ErrorHelper.rethrow(e); } } } switch (instantiatorDefinition.getType()) { case CONSTRUCTOR: return constructorBiInstantiator((ExecutableInstantiatorDefinition)instantiatorDefinition, injections); case METHOD: return methodBiInstantiator((ExecutableInstantiatorDefinition)instantiatorDefinition, injections); case BUILDER: return builderBiInstantiator((BuilderInstantiatorDefinition)instantiatorDefinition, injections, useAsmIfEnabled); default: throw new IllegalArgumentException("Unsupported executable type " + instantiatorDefinition); } } @SuppressWarnings("unchecked") public <S, T> Instantiator<S, T> getInstantiator(Type target, final Class<S> source, List<InstantiatorDefinition> constructors, Map<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> injections, boolean useAsmIfEnabled) throws SecurityException { final InstantiatorDefinition instantiatorDefinition = getSmallerConstructor(constructors); if (instantiatorDefinition == null) { throw new IllegalArgumentException("No constructor available for " + target); } return getInstantiator(instantiatorDefinition, source, injections, useAsmIfEnabled); } @SuppressWarnings("unchecked") public <S, T> Instantiator<S, T> getInstantiator(InstantiatorDefinition instantiatorDefinition, Class<S> source, Map<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> injections, boolean useAsmIfEnabled) { checkParameters(instantiatorDefinition, injections.keySet()); if (asmFactory != null && useAsmIfEnabled) { if (instantiatorDefinition instanceof ExecutableInstantiatorDefinition) { ExecutableInstantiatorDefinition executableInstantiatorDefinition = (ExecutableInstantiatorDefinition) instantiatorDefinition; Member executable = executableInstantiatorDefinition.getExecutable(); if (Modifier.isPublic(executable.getModifiers())) { try { return asmFactory.createInstantiator(source, executableInstantiatorDefinition, injections); } catch (Exception e) { // fall back on reflection if (failOnAsmError) ErrorHelper.rethrow(e); } } } else { try { return asmFactory.createInstantiator(source, (BuilderInstantiatorDefinition)instantiatorDefinition, injections); } catch (Exception e) { // fall back on reflection if (failOnAsmError) ErrorHelper.rethrow(e); } } } switch (instantiatorDefinition.getType()) { case CONSTRUCTOR: return constructorInstantiator((ExecutableInstantiatorDefinition)instantiatorDefinition, injections); case METHOD: return methodInstantiator((ExecutableInstantiatorDefinition)instantiatorDefinition, injections); case BUILDER: return builderInstantiator((BuilderInstantiatorDefinition)instantiatorDefinition, injections, useAsmIfEnabled); default: throw new IllegalArgumentException("Unsupported executable type " + instantiatorDefinition); } } private void checkParameters(InstantiatorDefinition instantiatorDefinition, Set<Parameter> parameters) { for(Parameter p : parameters) { if (!instantiatorDefinition.hasParam(p)) { throw new IllegalArgumentException("Could not find " + p + " in " + instantiatorDefinition + " raise issue"); } } } @SuppressWarnings({"unchecked", "SuspiciousToArrayCall"}) private <S, T> Instantiator<S, T> builderInstantiator(BuilderInstantiatorDefinition instantiatorDefinition, Map<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> injections, boolean useAsmIfEnabled) { final Instantiator<Void, ?> buildInstantiator = getInstantiator(instantiatorDefinition.getBuilderInstantiator(), Void.class, new HashMap<org.simpleflatmapper.reflect.Parameter, Getter<? super Void, ?>>(), useAsmIfEnabled); List<MethodGetterPair<S>> chainedArguments = new ArrayList<MethodGetterPair<S>>(); List<MethodGetterPair<S>> unchainedArguments = new ArrayList<MethodGetterPair<S>>(); int i = 0; for(Map.Entry<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> e : injections.entrySet()) { final MethodGetterPair<S> arguments = new MethodGetterPair<S>(instantiatorDefinition.getSetters().get(e.getKey()), e.getValue()); if (Void.TYPE.equals(arguments.getMethod().getReturnType())) { unchainedArguments.add(arguments); } else { chainedArguments.add(arguments); } } return new BuilderInstantiator<S, T>(buildInstantiator, chainedArguments.toArray(new MethodGetterPair[0]), unchainedArguments.toArray(new MethodGetterPair[0]), instantiatorDefinition.getBuildMethod()); } private <S, T> Instantiator<S, T> methodInstantiator( ExecutableInstantiatorDefinition instantiatorDefinition, Map<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> injections) { Method m = (Method) instantiatorDefinition.getExecutable(); if (m.getParameterTypes().length == 0) { return new EmptyStaticMethodInstantiator<S, T>(m); } else { return new InjectStaticMethodInstantiator<S, T>(instantiatorDefinition, injections); } } @SuppressWarnings("unchecked") private <S, T> Instantiator<S, T> constructorInstantiator( ExecutableInstantiatorDefinition instantiatorDefinition, Map<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> injections) { Constructor<? extends T> c = (Constructor<? extends T>) instantiatorDefinition.getExecutable(); if (c.getParameterTypes().length == 0) { return new EmptyConstructorInstantiator<S, T>(c); } else { return new InjectConstructorInstantiator<S, T>(instantiatorDefinition, injections); } } @SuppressWarnings({"unchecked", "SuspiciousToArrayCall"}) private <S1, S2, T> BiInstantiator<S1, S2, T> builderBiInstantiator(BuilderInstantiatorDefinition instantiatorDefinition, Map<org.simpleflatmapper.reflect.Parameter, BiFunction<? super S1, ? super S2, ?>> injections, boolean useAsmIfEnabled) { final Instantiator<Void, ?> buildInstantiator = getInstantiator(instantiatorDefinition.getBuilderInstantiator(), Void.class, new HashMap<org.simpleflatmapper.reflect.Parameter, Getter<? super Void, ?>>(), useAsmIfEnabled); List<MethodBiFunctionPair<S1, S2>> chainedArguments = new ArrayList<MethodBiFunctionPair<S1, S2>>(); List<MethodBiFunctionPair<S1, S2>> unchainedArguments = new ArrayList<MethodBiFunctionPair<S1, S2>>(); int i = 0; for(Map.Entry<org.simpleflatmapper.reflect.Parameter, BiFunction<? super S1, ? super S2, ?>> e : injections.entrySet()) { final MethodBiFunctionPair<S1, S2> arguments = new MethodBiFunctionPair<S1, S2>(instantiatorDefinition.getSetters().get(e.getKey()), e.getValue()); if (Void.TYPE.equals(arguments.getMethod().getReturnType())) { unchainedArguments.add(arguments); } else { chainedArguments.add(arguments); } } return new BuilderBiInstantiator<S1, S2, T>(buildInstantiator, chainedArguments.toArray(new MethodBiFunctionPair[0]), unchainedArguments.toArray(new MethodBiFunctionPair[0]), instantiatorDefinition.getBuildMethod()); } private <S1, S2, T> BiInstantiator<S1, S2, T> methodBiInstantiator( ExecutableInstantiatorDefinition instantiatorDefinition, Map<org.simpleflatmapper.reflect.Parameter, BiFunction<? super S1, ? super S2, ?>> injections) { Method m = (Method) instantiatorDefinition.getExecutable(); if (m.getParameterTypes().length == 0) { return new EmptyStaticMethodBiInstantiator<S1, S2, T>(m); } else { return new InjectStaticMethodBiInstantiator<S1, S2, T>(instantiatorDefinition, injections); } } @SuppressWarnings("unchecked") private <S1, S2, T> BiInstantiator<S1, S2, T> constructorBiInstantiator( ExecutableInstantiatorDefinition instantiatorDefinition, Map<org.simpleflatmapper.reflect.Parameter, BiFunction<? super S1, ? super S2, ?>> injections) { Constructor<? extends T> c = (Constructor<? extends T>) instantiatorDefinition.getExecutable(); if (c.getParameterTypes().length == 0) { return new EmptyConstructorBiInstantiator<S1, S2, T>(c); } else { return new InjectConstructorBiInstantiator<S1, S2, T>(instantiatorDefinition, injections); } } public static InstantiatorDefinition getSmallerConstructor(final List<InstantiatorDefinition> constructors) { if (constructors == null) { return null; } InstantiatorDefinition selectedConstructor = null; for(InstantiatorDefinition c : constructors) { if (selectedConstructor == null || InstantiatorDefinitions.COMPARATOR.compare(c, selectedConstructor) < 0) { selectedConstructor = c; } } return selectedConstructor; } public <S, T> Instantiator<S, T> getArrayInstantiator(final Class<?> elementType, final int length) { return new ArrayInstantiator<S, T>(elementType, length); } public <S1, S2, T> BiInstantiator<S1, S2, T> getArrayBiInstantiator(final Class<?> elementType, final int length) { return new ArrayBiInstantiator<S1, S2, T>(elementType, length); } @SuppressWarnings("unchecked") public <S, T> Instantiator<S, T> getOneArgIdentityInstantiator(InstantiatorDefinition id) { if (id.getParameters().length != 1) { throw new IllegalArgumentException("Definition does not have one param " + Arrays.asList(id.getParameters())); } Map<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>> injections = new HashMap<org.simpleflatmapper.reflect.Parameter, Getter<? super S, ?>>(); injections.put(id.getParameters()[0], new IdentityGetter<S>()); return getInstantiator(id, (Class<S>) id.getParameters()[0].getType(), injections, true); } private static final class ArrayInstantiator<S, T> implements Instantiator<S, T> { private final Class<?> elementType; private final int length; public ArrayInstantiator(Class<?> elementType, int length) { this.elementType = elementType; this.length = length; } @SuppressWarnings("unchecked") @Override public T newInstance(S s) throws Exception { return (T) Array.newInstance(elementType, length); } } private static final class ArrayBiInstantiator<S1, S2, T> implements BiInstantiator<S1, S2, T> { private final Class<?> elementType; private final int length; public ArrayBiInstantiator(Class<?> elementType, int length) { this.elementType = elementType; this.length = length; } @SuppressWarnings("unchecked") @Override public T newInstance(S1 s1, S2 s2) throws Exception { return (T) Array.newInstance(elementType, length); } } }