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

se.l4.commons.serialization.internal.reflection.FactoryDefinition Maven / Gradle / Ivy

package se.l4.commons.serialization.internal.reflection;

import java.lang.annotation.Annotation;
import java.lang.reflect.Constructor;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.lang.reflect.Parameter;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.function.Supplier;

import com.fasterxml.classmate.ResolvedType;
import com.fasterxml.classmate.members.ResolvedConstructor;
import com.google.common.base.Defaults;
import com.google.common.base.MoreObjects;
import com.google.common.primitives.Primitives;

import se.l4.commons.serialization.Expose;
import se.l4.commons.serialization.Factory;
import se.l4.commons.serialization.SerializationException;
import se.l4.commons.serialization.SerializerCollection;
import se.l4.commons.types.InstanceException;

/**
 * Factory that can be used to create an instance of a certain object.
 *
 * @author Andreas Holstenson
 *
 */
public class FactoryDefinition
{
	private final Constructor raw;
	final Argument[] arguments;

	private final boolean hasSerializedFields;
	private final boolean isInjectable;

	public FactoryDefinition(
		Constructor raw,
		Argument[] arguments,
		boolean hasSerializedFields,
		boolean isInjectable
	)
	{
		this.raw = raw;
		this.arguments = arguments;
		this.hasSerializedFields = hasSerializedFields;
		this.isInjectable = isInjectable;
	}

	public static  FactoryDefinition resolve(
		SerializerCollection collection,
		se.l4.commons.serialization.spi.Type parentType,
		Map fields,
		Map nonRenamed,
		ResolvedConstructor constructor
	)
	{
		List args = new ArrayList<>();

		Constructor raw = constructor.getRawMember();

		String[] names = findNames(raw);

		Annotation[][] annotations = raw.getParameterAnnotations();

		// Figure out if we are injectable
		boolean isInjectable = constructor.getArgumentCount() == 0;
		for(Annotation a : constructor.getRawMember().getAnnotations())
		{
			if(a.annotationType() == Factory.class)
			{
				isInjectable = true;
			}
			else if(a.annotationType().getSimpleName().equals("Inject"))
			{
				isInjectable = true;
			}
		}

		// Get if any fields are going to be serialized
		boolean hasSerializedFields = false;
		for(int i=0, n=constructor.getArgumentCount(); i(
			raw,
			arguments,
			hasSerializedFields,
			isInjectable
		);
	}

	private static String[] findNames(Constructor c)
	{
		String[] names = findNamesViaConstructorProperties(c);
		if(names != null) return names;

		return findNamesViaReflection(c);
	}

	private static String[] findNamesViaConstructorProperties(Constructor c)
	{
		for(Annotation a : c.getAnnotations())
		{
			if("java.beans.ConstructorProperties".equals(a.annotationType().getName()))
			{
				// This is the annotation we are looking for
				try
				{
					Method method = a.annotationType().getMethod("value");
					return (String[]) method.invoke(a);
				}
				catch(NoSuchMethodException | SecurityException | IllegalAccessException | IllegalArgumentException | InvocationTargetException e)
				{
					// Ignore that we can't accerss this
					return null;
				}
			}
		}

		return null;
	}

	private static String[] findNamesViaReflection(Constructor c)
	{
		Parameter[] params = c.getParameters();
		String[] result = new String[params.length];
		for(int i=0, n=params.length; i data)
	{
		if(! hasSerializedFields)
		{
			return 0;
		}

		int score = 0;

		for(Argument arg : arguments)
		{
			if(arg instanceof FactoryDefinition.SerializedArgument)
			{
				if(data.containsKey(((SerializedArgument) arg).name))
				{
					score++;
				}
			}
		}

		return score;
	}

	/**
	 * Create a new instance using the given deserialized data. The data
	 * is only used if this factory has any serialized fields.
	 *
	 * @param data
	 * @return
	 */
	@SuppressWarnings("unchecked")
	public T create(Map data)
	{
		Object[] args = new Object[arguments.length];
		for(int i=0, n=args.length; i data);
	}

	static class SerializedArgument
		implements Argument
	{
		final Class type;
		final String name;

		public SerializedArgument(Class type, String name)
		{
			this.type = type;
			this.name = name;
		}

		@Override
		public Object getValue(Map data)
		{
			Object value = data.get(name);
			if(value == null && type.isPrimitive())
			{
				return Defaults.defaultValue(type);
			}

			return value;
		}
	}

	private static class InjectedArgument
		implements Argument
	{
		private final Supplier supplier;

		public InjectedArgument(SerializerCollection collection, Type type, Annotation[] annotations)
		{
			supplier = collection.getInstanceFactory().supplier(type, annotations);
		}

		@Override
		public Object getValue(Map data)
		{
			try
			{
				return supplier.get();
			}
			catch(InstanceException e)
			{
				throw new SerializationException("Unable to get object for argument; " + e.getMessage(), e);
			}
		}
	}

	@Override
	public String toString()
	{
		return MoreObjects.toStringHelper(this)
			.add("constructor", raw)
			.toString();
	}
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy