All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.simpleflatmapper.reflect.InstantiatorFactory Maven / Gradle / Ivy

package org.simpleflatmapper.reflect;

import org.simpleflatmapper.reflect.asm.AsmFactory;
import org.simpleflatmapper.reflect.asm.AsmFactoryProvider;
import org.simpleflatmapper.reflect.asm.BiInstantiatorKey;
import org.simpleflatmapper.reflect.asm.InstantiatorKey;
import org.simpleflatmapper.reflect.getter.IdentityGetter;
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.reflect.instantiator.KotlinDefaultConstructorInstantiatorDefinition;
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 AsmFactoryProvider asmFactory;

	private final boolean failOnAsmError;
	
	public InstantiatorFactory(final AsmFactoryProvider asmFactory) {
		this(asmFactory, false);
	}

	public InstantiatorFactory(AsmFactoryProvider asmFactory, boolean faileOnAsmError) {
		this.asmFactory = asmFactory;
		this.failOnAsmError = faileOnAsmError;
	}


	@SuppressWarnings("unchecked")
	public  BiInstantiator getBiInstantiator(Type target, final Class s1, final Class s2, List constructors, Map> injections, boolean useAsmIfEnabled, boolean builderIgnoresNullValues) throws SecurityException {
		final InstantiatorDefinition instantiatorDefinition = getSmallerConstructor(constructors, injections.keySet());

		if (instantiatorDefinition == null) {
			throw new IllegalArgumentException("No constructor available for " + target);
		}
		return this.getBiInstantiator(instantiatorDefinition, s1, s2, injections, useAsmIfEnabled, builderIgnoresNullValues);


	}

	@SuppressWarnings("unchecked")
	public  BiInstantiator getBiInstantiator(InstantiatorDefinition instantiatorDefinition, Class s1, Class s2, Map> injections, boolean useAsmIfEnabled, boolean builderIgnoresNullValues) {
		checkParameters(instantiatorDefinition, injections.keySet());

		if (instantiatorDefinition instanceof KotlinDefaultConstructorInstantiatorDefinition) {
			KotlinDefaultConstructorInstantiatorDefinition kid = (KotlinDefaultConstructorInstantiatorDefinition) instantiatorDefinition;
			injections =  new HashMap>(injections);
			kid.addDefaultValueFlagBi(injections);
			instantiatorDefinition = kid.getDefaultValueConstructor();
		}
		
		if (asmFactory != null  && useAsmIfEnabled) {
			ClassLoader targetClassLoader = BiInstantiatorKey.getDeclaringClass(instantiatorDefinition).getClassLoader();
			if (instantiatorDefinition instanceof ExecutableInstantiatorDefinition) {
				ExecutableInstantiatorDefinition executableInstantiatorDefinition = (ExecutableInstantiatorDefinition) instantiatorDefinition;
				Member executable = executableInstantiatorDefinition.getExecutable();
				if (Modifier.isPublic(executable.getModifiers())) {
					try {
						return asmFactory.getAsmFactory(targetClassLoader).createBiInstantiator(s1, s2, executableInstantiatorDefinition, injections, builderIgnoresNullValues);
					} catch (Exception e) {
						// fall back on reflection
						if (failOnAsmError) ErrorHelper.rethrow(e);
					}
				}
			} else {
				try {
					return asmFactory.getAsmFactory(targetClassLoader).createBiInstantiator(s1, s2, (BuilderInstantiatorDefinition)instantiatorDefinition, injections, builderIgnoresNullValues);
				} 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, builderIgnoresNullValues);
			default:
				throw new IllegalArgumentException("Unsupported executable type " + instantiatorDefinition);
		}
	}


	@SuppressWarnings("unchecked")
	public  Instantiator getInstantiator(Type target, final Class source, List constructors, Map> injections, boolean useAsmIfEnabled, boolean builderIgnoresNullValues) throws SecurityException {
		final InstantiatorDefinition instantiatorDefinition = getSmallerConstructor(constructors, injections.keySet());

		if (instantiatorDefinition == null) {
			throw new IllegalArgumentException("No constructor available for " + target);
		}
		return getInstantiator(instantiatorDefinition, source, injections, useAsmIfEnabled, builderIgnoresNullValues);


	}

	@SuppressWarnings("unchecked")
	public  Instantiator getInstantiator(InstantiatorDefinition instantiatorDefinition, Class source, Map> injections, boolean useAsmIfEnabled, boolean builderIgnoresNullValues) {
		for(Parameter p : injections.keySet()) {
			if (!instantiatorDefinition.hasParam(p)) {
				throw new IllegalArgumentException("Could not find " + p + " in " + instantiatorDefinition + " raise issue");
			}
		}
		
		if (instantiatorDefinition instanceof KotlinDefaultConstructorInstantiatorDefinition) {
			KotlinDefaultConstructorInstantiatorDefinition kid = (KotlinDefaultConstructorInstantiatorDefinition) instantiatorDefinition;
			injections = new HashMap>(injections);
			kid.addDefaultValueFlag(injections);
			instantiatorDefinition = kid.getDefaultValueConstructor();
		}
		
		if (asmFactory != null  && useAsmIfEnabled) {
			ClassLoader targetClassLoader = InstantiatorKey.getDeclaringClass(instantiatorDefinition).getClassLoader();
			if (instantiatorDefinition instanceof ExecutableInstantiatorDefinition) {
				ExecutableInstantiatorDefinition executableInstantiatorDefinition = (ExecutableInstantiatorDefinition) instantiatorDefinition;
				Member executable = executableInstantiatorDefinition.getExecutable();
				if (Modifier.isPublic(executable.getModifiers())) {
					try {
						return asmFactory.getAsmFactory(targetClassLoader).createInstantiator(source, executableInstantiatorDefinition, injections, builderIgnoresNullValues);
					} catch (Exception e) {
						// fall back on reflection
						if (failOnAsmError) ErrorHelper.rethrow(e);
					}
				}
			} else {
				try {
					return asmFactory.getAsmFactory(targetClassLoader).createInstantiator(source, (BuilderInstantiatorDefinition)instantiatorDefinition, injections, builderIgnoresNullValues);
				} 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, builderIgnoresNullValues);
			default:
				throw new IllegalArgumentException("Unsupported executable type " + instantiatorDefinition);
		}
	}

	private static boolean checkParameters(InstantiatorDefinition instantiatorDefinition, Set parameters) {
		for(Parameter p : parameters) {
			if (!instantiatorDefinition.hasParam(p)) {
				return false;
			}
		}
		return true;
	}

	@SuppressWarnings({"unchecked", "SuspiciousToArrayCall"})
	private  Instantiator builderInstantiator(BuilderInstantiatorDefinition instantiatorDefinition,
														  Map> injections, boolean useAsmIfEnabled, boolean builderIgnoresNullValues) {

		final Instantiator buildInstantiator =
				getInstantiator(instantiatorDefinition.getBuilderInstantiator(), Void.class,
				 new HashMap>(), useAsmIfEnabled, builderIgnoresNullValues);
		List> chainedArguments = new ArrayList>();
		List> unchainedArguments = new ArrayList>();

		for(Map.Entry> e : injections.entrySet()) {
			final MethodGetterPair arguments =
				new MethodGetterPair(instantiatorDefinition.getSetters().get(e.getKey()), e.getValue());
			if (Void.TYPE.equals(arguments.getMethod().getReturnType())) {
				unchainedArguments.add(arguments);
			} else {
				chainedArguments.add(arguments);
			}
		}

		return new BuilderInstantiator(buildInstantiator,
				chainedArguments.toArray(new MethodGetterPair[0]),
				unchainedArguments.toArray(new MethodGetterPair[0]),
				instantiatorDefinition.getBuildMethod(), builderIgnoresNullValues);
	}

	private  Instantiator methodInstantiator(
			ExecutableInstantiatorDefinition instantiatorDefinition,
			Map> injections) {
		Method m = (Method) instantiatorDefinition.getExecutable();
		if (m.getParameterTypes().length == 0) {
			return new EmptyStaticMethodInstantiator(m);
		} else {
			return new InjectStaticMethodInstantiator(instantiatorDefinition, injections);
		}
	}


	@SuppressWarnings("unchecked")
	private  Instantiator constructorInstantiator(
			ExecutableInstantiatorDefinition instantiatorDefinition,
			Map> injections) {
		Constructor c = (Constructor) instantiatorDefinition.getExecutable();
		if (c.getParameterTypes().length == 0) {
			return new EmptyConstructorInstantiator(c);
		} else {
			return new InjectConstructorInstantiator(instantiatorDefinition, injections);
		}
	}


	@SuppressWarnings({"unchecked", "SuspiciousToArrayCall"})
	public  BuilderBiInstantiator  builderBiInstantiator(BuilderInstantiatorDefinition instantiatorDefinition,
																		 Map> injections, boolean useAsmIfEnabled, boolean builderIgnoresNullValues) {

		final Instantiator buildInstantiator =
				getInstantiator(instantiatorDefinition.getBuilderInstantiator(), Void.class,
						new HashMap>(), useAsmIfEnabled, builderIgnoresNullValues);
		List> chainedArguments = new ArrayList>();
		List> unchainedArguments = new ArrayList>();

		int i = 0;
		for(Map.Entry> e : injections.entrySet()) {
			final MethodBiFunctionPair arguments =
					new MethodBiFunctionPair(instantiatorDefinition.getSetters().get(e.getKey()), e.getValue());
			if (Void.TYPE.equals(arguments.getMethod().getReturnType())) {
				unchainedArguments.add(arguments);
			} else {
				chainedArguments.add(arguments);
			}
		}

		return new BuilderBiInstantiator(buildInstantiator,
				chainedArguments.toArray(new MethodBiFunctionPair[0]),
				unchainedArguments.toArray(new MethodBiFunctionPair[0]),
				instantiatorDefinition.getBuildMethod(), builderIgnoresNullValues);
	}

	private  BiInstantiator  methodBiInstantiator(
			ExecutableInstantiatorDefinition instantiatorDefinition,
			Map> injections) {
		Method m = (Method) instantiatorDefinition.getExecutable();
		if (m.getParameterTypes().length == 0) {
			return new EmptyStaticMethodBiInstantiator(m);
		} else {
			return new InjectStaticMethodBiInstantiator(instantiatorDefinition, injections);
		}
	}


	@SuppressWarnings("unchecked")
	private  BiInstantiator  constructorBiInstantiator(
			ExecutableInstantiatorDefinition instantiatorDefinition,
			Map> injections) {
		Constructor c = (Constructor) instantiatorDefinition.getExecutable();
		if (c.getParameterTypes().length == 0) {
			return new EmptyConstructorBiInstantiator(c);
		} else {
			return new InjectConstructorBiInstantiator(instantiatorDefinition, injections);
		}
	}

	public static InstantiatorDefinition getSmallerConstructor(final List constructors, Set parameters) {
        if (constructors == null) {
            return null;
        }

		InstantiatorDefinition selectedConstructor = null;
		
		for(InstantiatorDefinition c : constructors) {
			if (checkParameters(c, parameters) && (selectedConstructor == null || InstantiatorDefinitions.COMPARATOR.compare(c, selectedConstructor) < 0)) {
				selectedConstructor = c;
			}
		}
		
		return selectedConstructor;
	}

	public  Instantiator getArrayInstantiator(final Class elementType, final int length) {
		return new ArrayInstantiator(elementType, length);
	}

	public  BiInstantiator getArrayBiInstantiator(final Class elementType, final int length) {
		return new ArrayBiInstantiator(elementType, length);
	}

	@SuppressWarnings("unchecked")
	public  Instantiator getOneArgIdentityInstantiator(InstantiatorDefinition id, boolean builderIgnoresNullValues) {
		if (id.getParameters().length != 1) {
			throw new IllegalArgumentException("Definition does not have one param " + Arrays.asList(id.getParameters()));
		}
		Map> injections = new HashMap>();
		injections.put(id.getParameters()[0], new IdentityGetter());
		return getInstantiator(id, (Class) id.getParameters()[0].getType(), injections, true, builderIgnoresNullValues);
	}


	private static final class ArrayInstantiator implements Instantiator {
		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 implements BiInstantiator {
		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);
		}
	}
}