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();
}
}