PodamFactoryImpl.java
/**
*
*/
package uk.co.jemos.podam.api;
import net.jcip.annotations.Immutable;
import net.jcip.annotations.NotThreadSafe;
import org.apache.commons.lang3.ArrayUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import uk.co.jemos.podam.api.DataProviderStrategy.Order;
import uk.co.jemos.podam.common.AttributeStrategy;
import uk.co.jemos.podam.common.Holder;
import uk.co.jemos.podam.common.ManufacturingContext;
import uk.co.jemos.podam.common.PodamConstants;
import uk.co.jemos.podam.common.PodamConstructor;
import uk.co.jemos.podam.exceptions.PodamMockeryException;
import uk.co.jemos.podam.typeManufacturers.TypeManufacturerUtil;
import java.lang.annotation.Annotation;
import java.lang.reflect.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.atomic.AtomicReference;
/**
* The PODAM factory implementation
*
* @author mtedone
*
* @since 1.0.0
*
*/
@NotThreadSafe
@Immutable
public class PodamFactoryImpl implements PodamFactory {
// ------------------->> Constants
private static final String RESOLVING_COLLECTION_EXCEPTION_STR = "An exception occurred while resolving the collection";
private static final String MAP_CREATION_EXCEPTION_STR = "An exception occurred while creating a Map object";
/** Application logger */
private static final Logger LOG = LoggerFactory.getLogger(PodamFactoryImpl.class);
// ------------------->> Instance / variables
/**
* External factory to delegate production this factory cannot handle
* <p>
* The default is {@link NullExternalFactory}.
* </p>
*/
private PodamFactory externalFactory
= NullExternalFactory.getInstance();
/**
* The strategy to use to fill data.
* <p>
* The default is {@link RandomDataProviderStrategyImpl}.
* </p>
*/
private DataProviderStrategy strategy
= new RandomDataProviderStrategyImpl();
/**
* The strategy to use to introspect data.
* <p>
* The default is {@link DefaultClassInfoStrategy}.
* </p>
*/
private ClassInfoStrategy classInfoStrategy
= DefaultClassInfoStrategy.getInstance();
// ------------------->> Constructors
/**
* Default constructor.
*/
public PodamFactoryImpl() {
this(NullExternalFactory.getInstance(),
new RandomDataProviderStrategyImpl());
}
/**
* Constructor with non-default strategy
*
* @param strategy
* The strategy to use to fill data
*/
public PodamFactoryImpl(DataProviderStrategy strategy) {
this(NullExternalFactory.getInstance(), strategy);
}
/**
* Constructor with non-default external factory
*
* @param externalFactory
* External factory to delegate production this factory cannot
* handle
*/
public PodamFactoryImpl(PodamFactory externalFactory) {
this(externalFactory, new RandomDataProviderStrategyImpl());
}
/**
* Full constructor.
*
* @param externalFactory
* External factory to delegate production this factory cannot
* handle
* @param strategy
* The strategy to use to fill data
*/
public PodamFactoryImpl(PodamFactory externalFactory,
DataProviderStrategy strategy) {
this.externalFactory = externalFactory;
this.strategy = strategy;
}
// ------------------->> Public methods
/**
* {@inheritDoc}
*/
@Override
public <T> T manufacturePojoWithFullData(Class<T> pojoClass, Type... genericTypeArgs) {
ManufacturingContext manufacturingCtx = new ManufacturingContext();
manufacturingCtx.getPojos().put(pojoClass, 1);
manufacturingCtx.setConstructorOrdering(Order.HEAVY_FIRST);
return doManufacturePojo(pojoClass, manufacturingCtx, genericTypeArgs);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T manufacturePojo(Class<T> pojoClass, Type... genericTypeArgs) {
ManufacturingContext manufacturingCtx = new ManufacturingContext();
manufacturingCtx.getPojos().put(pojoClass, 1);
return doManufacturePojo(pojoClass, manufacturingCtx, genericTypeArgs);
}
/**
* {@inheritDoc}
*/
@Override
public <T> T populatePojo(T pojo, Type... genericTypeArgs) {
ManufacturingContext manufacturingCtx = new ManufacturingContext();
manufacturingCtx.getPojos().put(pojo.getClass(), 1);
Type[] genericTypeArgsExtra = ManufacturingContext.fillTypeArgsMap(
manufacturingCtx, pojo.getClass(), genericTypeArgs);
try {
List<Annotation> annotations = null;
return this.populatePojoInternal(pojo, annotations,
manufacturingCtx, genericTypeArgsExtra);
} catch (InstantiationException e) {
throw new PodamMockeryException(e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new PodamMockeryException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new PodamMockeryException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new PodamMockeryException(e.getMessage(), e);
}
}
// ------------------->> Getters / Setters
/**
* {@inheritDoc}
*/
@Override
public DataProviderStrategy getStrategy() {
return strategy;
}
/**
* {@inheritDoc}
*/
@Override
public PodamFactory setStrategy(DataProviderStrategy strategy) {
this.strategy = strategy;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public ClassInfoStrategy getClassStrategy() {
return classInfoStrategy;
}
/**
* {@inheritDoc}
*/
@Override
public PodamFactory setClassStrategy(ClassInfoStrategy classInfoStrategy) {
this.classInfoStrategy = classInfoStrategy;
return this;
}
/**
* {@inheritDoc}
*/
@Override
public PodamFactory getExternalFactory() {
return externalFactory;
}
/**
* {@inheritDoc}
*/
@Override
public PodamFactory setExternalFactory(PodamFactory externalFactory) {
this.externalFactory = externalFactory;
return this;
}
// ------------------->> Private methods
/**
* It attempts to create an instance of the given class with a static method
* of the factory
* <p>
* This method attempts to instantiate POJO with a static method of provided
* factory, for example, getInstance().
* </p>
*
* @param <T>
* The type of Pojo class
* @param factoryClass
* The factory class, which will be used for POJO instantiation
* @param pojoClass
* The name of the class for which an instance filled with values
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
*
*
* @return An instance of the given class
* @throws IllegalArgumentException
* If an illegal argument was passed to the constructor
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*/
private <T> T instantiatePojoWithFactory(
Class<?> factoryClass, Class<T> pojoClass,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
// If no publicly accessible constructors are available,
// the best we can do is to find a constructor (e.g.
// getInstance())
Method[] declaredMethods = TypeManufacturerUtil.findSuitableConstructors(
factoryClass, pojoClass);
strategy.sort(declaredMethods, manufacturingCtx.getConstructorOrdering());
// A candidate factory method is a method which returns the
// Class type
// The parameters to pass to the method invocation
Object[] parameterValues = null;
for (Method candidateConstructor : declaredMethods) {
Object factoryInstance = null;
if (!Modifier.isStatic(candidateConstructor.getModifiers())) {
factoryInstance = manufacturePojo(factoryClass);
}
parameterValues = getParameterValuesForMethod(candidateConstructor,
pojoClass, manufacturingCtx, genericTypeArgs);
try {
@SuppressWarnings("unchecked")
T retValue = (T) candidateConstructor.invoke(factoryInstance,
parameterValues);
LOG.debug("Could create an instance using "
+ candidateConstructor);
return retValue;
} catch (Exception t) {
LOG.debug(
"PODAM could not create an instance for constructor: "
+ candidateConstructor
+ ". Will try another one...", t);
}
}
LOG.debug("For class {} PODAM could not possibly create"
+ " a value statically. Will try other means.",
pojoClass);
return null;
}
/**
* It creates and returns an instance of the given class if at least one of
* its constructors has been annotated with {@link PodamConstructor}
*
* @param <T>
* The type of the instance to return
*
* @param pojoClass
* The class of which an instance is required
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return an instance of the given class if at least one of its
* constructors has been annotated with {@link PodamConstructor}
* @throws SecurityException
* If an security was violated
*/
private <T> T instantiatePojo(Class<T> pojoClass,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws SecurityException {
T retValue = null;
Constructor<?>[] constructors = pojoClass.getConstructors();
if (constructors.length == 0 || Modifier.isAbstract(pojoClass.getModifiers())) {
/* No public constructors, we will try static factory methods */
try {
retValue = instantiatePojoWithFactory(
pojoClass, pojoClass, manufacturingCtx, genericTypeArgs);
} catch (Exception e) {
LOG.debug("We couldn't create an instance for pojo: "
+ pojoClass + " with factory methods, will "
+ " try non-public constructors.", e);
}
/* Then non-public constructors */
if (retValue == null) {
constructors = pojoClass.getDeclaredConstructors();
}
}
if (retValue == null) {
strategy.sort(constructors, manufacturingCtx.getConstructorOrdering());
for (Constructor<?> constructor : constructors) {
try {
Object[] parameterValues = getParameterValuesForConstructor(
constructor, pojoClass, manufacturingCtx,
genericTypeArgs);
// Security hack
if (!constructor.isAccessible()) {
constructor.setAccessible(true);
}
@SuppressWarnings("unchecked")
T tmp = (T) constructor.newInstance(parameterValues);
retValue = tmp;
LOG.debug("We could create an instance with constructor: "
+ constructor);
break;
} catch (Exception e) {
LOG.debug("We couldn't create an instance for pojo: {} with"
+ " constructor: {}. Will try with another one.",
pojoClass, constructor, e);
}
}
}
if (retValue == null) {
LOG.debug("For class {} PODAM could not possibly create"
+ " a value. Will try other means.", pojoClass);
}
return retValue;
}
/**
* Manufactures and populates the pojo class
*
* @param <T> The type of the instance to return
* @param pojoClass the class to instantiate
* @param manufacturingCtx the initialized manufacturing context
* @param genericTypeArgs generic arguments for the pojo class
* @return instance of @pojoClass or null in case it cannot be instantiated
*/
private <T> T doManufacturePojo(Class<T> pojoClass,
ManufacturingContext manufacturingCtx, Type... genericTypeArgs) {
try {
Class<?> declaringClass = null;
Object declaringInstance = null;
AttributeMetadata pojoMetadata = new AttributeMetadata(pojoClass,
pojoClass, genericTypeArgs, declaringClass, declaringInstance);
return this.manufacturePojoInternal(pojoClass, pojoMetadata,
manufacturingCtx, genericTypeArgs);
} catch (InstantiationException e) {
throw new PodamMockeryException(e.getMessage(), e);
} catch (IllegalAccessException e) {
throw new PodamMockeryException(e.getMessage(), e);
} catch (InvocationTargetException e) {
throw new PodamMockeryException(e.getMessage(), e);
} catch (ClassNotFoundException e) {
throw new PodamMockeryException(e.getMessage(), e);
}
}
/**
* Generic method which returns an instance of the given class filled with
* values dictated by the strategy
*
* @param <T>
* The type for which a filled instance is required
*
* @param pojoClass
* The name of the class for which an instance filled with values
* is required
* @param pojoMetadata
* attribute metadata for POJOs produced recursively
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return An instance of <T> filled with dummy values
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If manufactured class cannot be loaded
* @throws PodamMockeryException
* if a problem occurred while creating a POJO instance or while
* setting its state
*/
private <T> T manufacturePojoInternal(Class<T> pojoClass,
AttributeMetadata pojoMetadata, ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
// reuse object from memoization table
@SuppressWarnings("unchecked")
T objectToReuse = (T) strategy.getMemoizedObject(pojoMetadata);
if (objectToReuse != null) {
LOG.debug("Fetched memoized object for {} with parameters {}",
pojoClass, Arrays.toString(genericTypeArgs));
return objectToReuse;
} else {
LOG.debug("Manufacturing {} with parameters {}",
pojoClass, Arrays.toString(genericTypeArgs));
}
manufacturingCtx.backupTypeArgsMap(manufacturingCtx.createEmptyTypeArgsMap());
Type[] genericTypeArgsExtra = ManufacturingContext.fillTypeArgsMap(
manufacturingCtx, pojoClass, genericTypeArgs);
T retValue = (T) strategy.getTypeValue(pojoMetadata, manufacturingCtx, pojoClass);
if (null == retValue && !pojoClass.isInterface()) {
try {
retValue = instantiatePojo(pojoClass, manufacturingCtx,
genericTypeArgsExtra);
} catch (SecurityException e) {
throw new PodamMockeryException(
"Security exception while applying introspection.", e);
}
}
if (retValue == null) {
retValue = getValueForAbstractType(pojoClass, pojoMetadata,
manufacturingCtx, genericTypeArgs);
} else {
// update memoization cache with new object
// the reference is stored before properties are set so that recursive
// properties can use it
strategy.cacheMemoizedObject(pojoMetadata, retValue);
List<Annotation> annotations = null;
populatePojoInternal(retValue, annotations, manufacturingCtx,
genericTypeArgsExtra);
}
manufacturingCtx.restoreTypeArgsMap();
return retValue;
}
/**
* Fills given class filled with values dictated by the strategy
*
* @param <T>
* The type for which should be populated
* @param pojo
* An instance to be filled with dummy values
* @param annotations
* a list of annotations attached to this POJO defined elsewhere
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return An instance of <T> filled with dummy values
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If manufactured class cannot be loaded
*/
private <T> T populatePojoInternal(T pojo, List<Annotation> annotations,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
LOG.debug("Populating pojo {}", pojo.getClass());
Class<?> pojoClass = pojo.getClass();
if (pojoClass.isArray()) {
if (null == annotations) {
annotations = new ArrayList<Annotation>();
}
String attributeName = null;
fillArray(pojo, attributeName,
pojoClass.getClass().getComponentType(),
pojoClass.getClass().getComponentType(),
annotations,
manufacturingCtx);
} else if (pojo instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Object> collection = (Collection<Object>) pojo;
AtomicReference<Type[]> elementGenericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
Class<?> elementTypeClass = findInheretedCollectionElementType(collection,
manufacturingCtx, elementGenericTypeArgs, genericTypeArgs);
if (null == annotations) {
annotations = new ArrayList<Annotation>();
}
for (Annotation annotation : collection.getClass().getAnnotations()) {
annotations.add(annotation);
}
String attributeName = null;
fillCollection(manufacturingCtx, annotations, attributeName,
collection, elementTypeClass, elementGenericTypeArgs.get());
} else if (pojo instanceof Map) {
@SuppressWarnings("unchecked")
Map<Object,Object> map = (Map<Object,Object>)pojo;
MapArguments mapArguments = findInheretedMapElementType(
map, manufacturingCtx, genericTypeArgs);
if (null != annotations) {
mapArguments.getAnnotations().addAll(annotations);
}
fillMap(mapArguments, manufacturingCtx);
}
ClassInfo classInfo = classInfoStrategy.getClassInfo(pojo.getClass());
Set<ClassAttribute> classAttributes = classInfo.getClassAttributes();
for (ClassAttribute attribute : classAttributes) {
if (!populateReadWriteField(pojo, attribute, manufacturingCtx)) {
populateReadOnlyField(pojo, attribute, manufacturingCtx, genericTypeArgs);
}
}
// It executes any extra methods
Collection<Method> extraMethods = classInfoStrategy.getExtraMethods(pojoClass);
if (null != extraMethods) {
for (Method extraMethod : extraMethods) {
Object[] args = getParameterValuesForMethod(extraMethod, pojoClass,
manufacturingCtx, genericTypeArgs);
extraMethod.invoke(pojo, args);
}
}
return pojo;
}
/**
* Fills a field with a getter
*
* @param <T>
* The type for which should be populated
* @param pojo
* The POJO being filled with values
* @param attribute
* a attribute we are filling
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return true, if attribute was found and populated
* @throws ClassNotFoundException
* If class being manufactured cannot be loaded
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
*/
private <T> boolean populateReadOnlyField(T pojo, ClassAttribute attribute,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Method getter = PodamUtils.selectLatestMethod(attribute.getGetters());
if (getter == null) {
return false;
}
if (getter.getGenericParameterTypes().length > 0) {
LOG.warn("Skipping invalid getter {}", getter);
return false;
}
Class<?> pojoType = getter.getReturnType();
if (pojoType.isPrimitive()) {
/* TODO: non-zero values should be fine */
return false;
}
Object fieldValue = null;
try {
fieldValue = getter.invoke(pojo, PodamConstants.NO_ARGS);
} catch(Exception e) {
LOG.debug("Cannot access {}, skipping", getter);
}
if (fieldValue != null) {
LOG.debug("Populating read-only field {}", getter);
Class<?> fieldClass = fieldValue.getClass();
Integer depth = manufacturingCtx.getPojos().get(fieldClass);
if (depth == null) {
depth = 0;
}
if (depth < strategy.getMaxDepth(fieldClass)) {
Type[] genericTypeArgsAll;
Type genericPojoType = getter.getGenericReturnType();
final boolean cloneTypeArgsMap = (genericPojoType instanceof ParameterizedType);
if (cloneTypeArgsMap) {
genericTypeArgsAll = manufacturingCtx.cloneTypeArgsMap(
pojoType, (ParameterizedType) genericPojoType, genericTypeArgs);
} else {
genericTypeArgsAll = genericTypeArgs;
}
List<Annotation> pojoAttributeAnnotations =
PodamUtils.getAttributeAnnotations(
attribute.getAttribute(), getter);
manufacturingCtx.getPojos().put(fieldClass, depth + 1);
populatePojoInternal(fieldValue, pojoAttributeAnnotations,
manufacturingCtx, genericTypeArgsAll);
manufacturingCtx.getPojos().put(fieldClass, depth);
if (cloneTypeArgsMap) {
manufacturingCtx.restoreTypeArgsMap();
}
} else {
LOG.warn("Loop of depth " + depth + " in filling read-only field {} detected.",
getter);
}
return true;
} else {
return false;
}
}
/**
* Fills a field with a setter
*
* @param <T>
* The type for which should be populated
* @param pojo
* The POJO being filled with values
* @param attribute
* a attribute we are filling
* @param manufacturingCtx
* the manufacturing context
* @return true, if attribute was found and populated
* @throws ClassNotFoundException
* If class being manufactured cannot be loaded
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
*/
private <T> boolean populateReadWriteField(T pojo, ClassAttribute attribute,
ManufacturingContext manufacturingCtx)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Method setter = PodamUtils.selectLatestMethod(attribute.getSetters());
if (setter == null) {
return false;
}
Class<?>[] parameterTypes = setter.getParameterTypes();
if (parameterTypes.length != 1) {
// According to JavaBeans standards, setters should have only
// one argument
LOG.warn("Skipping setter with non-single arguments {}",
setter);
return false;
}
LOG.debug("Populating read-write field {}", setter);
// A class which has got an attribute to itself (e.g.
// recursive hierarchies)
Class<?> attributeType = parameterTypes[0];
// If an attribute has been annotated with
// PodamAttributeStrategy, it takes the precedence over any
// other strategy. Additionally we don't pass the attribute
// metadata for value customisation; if user went to the extent
// of specifying a PodamAttributeStrategy annotation for an
// attribute they are already customising the value assigned to
// that attribute.
List<Annotation> pojoAttributeAnnotations
= PodamUtils.getAttributeAnnotations(
attribute.getAttribute(), setter);
AttributeStrategy<?> attributeStrategy
= TypeManufacturerUtil.findAttributeStrategy(strategy, pojoAttributeAnnotations, attributeType);
if (null == attributeStrategy) {
attributeStrategy = strategy.getStrategyForAttribute(attribute);
}
Object setterArg = null;
if (null != attributeStrategy) {
setterArg = TypeManufacturerUtil.returnAttributeDataStrategyValue(
attributeType, pojoAttributeAnnotations, attributeStrategy);
} else {
AtomicReference<Type[]> typeGenericTypeArgs
= new AtomicReference<Type[]>(PodamConstants.NO_TYPES);
// If the parameter is a generic parameterized type resolve
// the actual type arguments
Type genericType = setter.getGenericParameterTypes()[0];
final Type[] typeArguments;
if (!(genericType instanceof GenericArrayType)) {
attributeType = TypeManufacturerUtil.resolveGenericParameter(genericType,
manufacturingCtx, typeGenericTypeArgs);
typeArguments = typeGenericTypeArgs.get();
} else {
typeArguments = PodamConstants.NO_TYPES;
}
if (!Collection.class.isAssignableFrom(attributeType) && !Map.class.isAssignableFrom(attributeType)) {
for (int i = 0; i < typeArguments.length; i++) {
if (typeArguments[i] instanceof TypeVariable) {
Class<?> resolvedType = TypeManufacturerUtil.resolveGenericParameter(typeArguments[i],
manufacturingCtx, typeGenericTypeArgs);
typeArguments[i] = resolvedType;
}
}
}
setterArg = manufactureAttributeValue(pojo, manufacturingCtx,
attributeType, genericType,
pojoAttributeAnnotations, attribute.getName(),
typeArguments);
}
try {
setter.invoke(pojo, setterArg);
} catch(IllegalAccessException e) {
LOG.warn("{} is not accessible. Setting it to accessible."
+ " However this is a security hack and your code"
+ " should really adhere to JavaBeans standards.",
setter.toString());
setter.setAccessible(true);
setter.invoke(pojo, setterArg);
}
return true;
}
/**
* It manufactures and returns the value for a POJO attribute.
*
*
* @param pojo
* The POJO being filled with values
* @param manufacturingCtx
* the manufacturing context
* @param attributeType
* The type of the attribute for which a value is being
* manufactured
* @param genericAttributeType
* The generic type of the attribute for which a value is being
* manufactured
* @param annotations
* The annotations for the attribute being considered
* @param attributeName
* The attribute name
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return The value for an attribute
*
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws IllegalArgumentException
* <ul>
* <li>If an illegal argument was passed</li>
* <li>If an invalid value was set for a precise value in an
* annotation and such value could not be converted to the
* desired type</li>
* </ul>
* @throws ClassNotFoundException
* If class being manufactured cannot be loaded
*
*/
private Object manufactureAttributeValue(Object pojo,
ManufacturingContext manufacturingCtx, Class<?> attributeType,
Type genericAttributeType, List<Annotation> annotations,
String attributeName,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Object attributeValue = null;
Class<?> pojoClass = (pojo instanceof Class ? (Class<?>) pojo : pojo.getClass());
Class<?> realAttributeType;
AtomicReference<Type[]> elementGenericTypeArgs = new AtomicReference<Type[]>(PodamConstants.NO_TYPES);
if (attributeType != genericAttributeType
&& Object.class.equals(attributeType)
&& genericAttributeType instanceof TypeVariable) {
realAttributeType = TypeManufacturerUtil.resolveGenericParameter(genericAttributeType,
manufacturingCtx, elementGenericTypeArgs);
} else {
realAttributeType = attributeType;
}
Type[] genericTypeArgsAll;
if (elementGenericTypeArgs.get().length > 0) {
genericTypeArgsAll = ArrayUtils.addAll(elementGenericTypeArgs.get(), genericTypeArgs);
} else {
genericTypeArgsAll = genericTypeArgs;
}
genericTypeArgsAll = TypeManufacturerUtil.mergeActualAndSuppliedGenericTypes(
attributeType, genericAttributeType, genericTypeArgsAll, manufacturingCtx);
AttributeMetadata attributeMetadata = new AttributeMetadata(
attributeName, realAttributeType, genericAttributeType,
genericTypeArgsAll, annotations, pojoClass, pojo);
if (realAttributeType.isArray()) {
// Array type
attributeValue = resolveArrayElementValue(pojo, manufacturingCtx,
attributeMetadata);
// Collection
} else if (Collection.class.isAssignableFrom(realAttributeType)) {
attributeValue = resolveCollectionValueWhenCollectionIsPojoAttribute(
pojo, manufacturingCtx, attributeMetadata);
// Map
} else if (Map.class.isAssignableFrom(realAttributeType)) {
attributeValue = resolveMapValueWhenMapIsPojoAttribute(pojo,
manufacturingCtx, attributeMetadata);
}
// For any other type, we use the PODAM strategy
if (attributeValue == null) {
Integer depth = manufacturingCtx.getPojos().get(realAttributeType);
if (depth == null) {
depth = 0;
}
if (depth < strategy.getMaxDepth(pojoClass)) {
manufacturingCtx.getPojos().put(realAttributeType, depth + 1);
attributeValue = this.manufacturePojoInternal(
realAttributeType, attributeMetadata, manufacturingCtx, genericTypeArgsAll);
manufacturingCtx.getPojos().put(realAttributeType, depth);
} else {
attributeValue = resortToExternalFactory(manufacturingCtx,
"Loop of depth " + depth + " in {} production detected. Resorting to {} external factory",
realAttributeType, genericTypeArgsAll);
}
}
return attributeValue;
}
/**
* Delegates POJO manufacturing to an external factory
*
* @param <T>
* The type of the instance to return
* @param manufacturingCtx
* the manufacturing context
* @param msg
* Message to log, must contain two parameters
* @param pojoClass
* The class of which an instance is required
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return instance of POJO produced by external factory or null
*/
private <T> T resortToExternalFactory(ManufacturingContext manufacturingCtx,
String msg, Class<T> pojoClass,
Type... genericTypeArgs) {
LOG.warn(msg, pojoClass, externalFactory.getClass().getName());
if (manufacturingCtx.getConstructorOrdering() == Order.HEAVY_FIRST) {
return externalFactory.manufacturePojoWithFullData(pojoClass, genericTypeArgs);
} else {
return externalFactory.manufacturePojo(pojoClass, genericTypeArgs);
}
}
/**
* It returns a collection of some sort with some data in it.
*
*
* @param pojo
* The POJO being analyzed
* @param manufacturingCtx
* the manufacturing context
* @param attributeMetadata
* The attribute's metadata
* @return a collection of some sort with some data in it
* @throws PodamMockeryException
* An exception occurred while resolving the collection
* @throws IllegalArgumentException
* If the field name is null or empty
*/
private Collection<? super Object> resolveCollectionValueWhenCollectionIsPojoAttribute(
Object pojo, ManufacturingContext manufacturingCtx,
AttributeMetadata attributeMetadata) {
String attributeName = attributeMetadata.getAttributeName();
// This needs to be generic because collections can be of any type
Collection<Object> defaultValue = null;
if (null != pojo && null != attributeName && !Character.isDigit(attributeName.charAt(0))) {
Object fieldValue = PodamUtils.getFieldValue(pojo, attributeName);
if (null != fieldValue) {
if (!(fieldValue instanceof Collection)) {
fieldValue = PodamUtils.getFieldValueWithGetter(pojo, attributeName);
}
if (fieldValue instanceof Collection) {
@SuppressWarnings("unchecked")
Collection<Object> tmp = (Collection<Object>)fieldValue;
defaultValue = tmp;
} else if (null != fieldValue) {
LOG.warn("Obtained non-collection field default value {}", fieldValue.getClass());
}
}
}
Collection<Object> retValue = null;
if (null != defaultValue &&
(defaultValue.getClass().getModifiers() & Modifier.PRIVATE) == 0) {
/* Default collection, which is not immutable */
retValue = defaultValue;
} else {
@SuppressWarnings("unchecked")
Class<Collection<Object>> collectionType
= (Class<Collection<Object>>) attributeMetadata.getAttributeType();
retValue = strategy.getTypeValue(attributeMetadata,
manufacturingCtx, collectionType);
if (null != retValue && null != defaultValue) {
retValue.addAll(defaultValue);
}
}
if (null == retValue) {
return null;
}
try {
Class<?> typeClass = null;
AtomicReference<Type[]> elementGenericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
if (ArrayUtils.isEmpty(attributeMetadata.getAttrGenericArgs())) {
typeClass = findInheretedCollectionElementType(retValue,
manufacturingCtx, elementGenericTypeArgs, attributeMetadata.getAttrGenericArgs());
} else {
Type actualTypeArgument = attributeMetadata.getAttrGenericArgs()[0];
typeClass = TypeManufacturerUtil.resolveGenericParameter(actualTypeArgument,
manufacturingCtx, elementGenericTypeArgs);
}
fillCollection(manufacturingCtx,
attributeMetadata.getAttributeAnnotations(), attributeName,
retValue, typeClass, elementGenericTypeArgs.get());
} catch (SecurityException e) {
throw new PodamMockeryException(RESOLVING_COLLECTION_EXCEPTION_STR,
e);
} catch (IllegalArgumentException e) {
throw new PodamMockeryException(RESOLVING_COLLECTION_EXCEPTION_STR,
e);
} catch (InstantiationException e) {
throw new PodamMockeryException(RESOLVING_COLLECTION_EXCEPTION_STR,
e);
} catch (IllegalAccessException e) {
throw new PodamMockeryException(RESOLVING_COLLECTION_EXCEPTION_STR,
e);
} catch (ClassNotFoundException e) {
throw new PodamMockeryException(RESOLVING_COLLECTION_EXCEPTION_STR,
e);
} catch (InvocationTargetException e) {
throw new PodamMockeryException(RESOLVING_COLLECTION_EXCEPTION_STR,
e);
}
return retValue;
}
/**
* Tries to find collection element type from collection object
*
* @param collection
* The collection to be filled
* @param manufacturingCtx
* the manufacturing context
* @param elementGenericTypeArgs
* parameter to return generic arguments of collection element
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return
* class type of collection element
*/
private Class<?> findInheretedCollectionElementType(
Collection<Object> collection, ManufacturingContext manufacturingCtx,
AtomicReference<Type[]> elementGenericTypeArgs,
Type... genericTypeArgs) {
Class<?> pojoClass = collection.getClass();
Class<?> collectionClass = pojoClass;
Type[] typeParams = collectionClass.getTypeParameters();
main : while (typeParams.length < 1) {
for (Type genericIface : collectionClass.getGenericInterfaces()) {
Class<?> clazz = TypeManufacturerUtil.resolveGenericParameter(
genericIface, manufacturingCtx, elementGenericTypeArgs);
if (Collection.class.isAssignableFrom(clazz)) {
collectionClass = clazz;
typeParams = elementGenericTypeArgs.get();
continue main;
}
}
Type type = collectionClass.getGenericSuperclass();
if (type != null) {
Class<?> clazz = TypeManufacturerUtil.resolveGenericParameter(
type, manufacturingCtx, elementGenericTypeArgs);
if (Collection.class.isAssignableFrom(clazz)) {
collectionClass = clazz;
typeParams = elementGenericTypeArgs.get();
continue main;
}
}
if (Collection.class.equals(collectionClass)) {
LOG.warn("Collection {} doesn't have generic types,"
+ "will use Object instead", pojoClass);
typeParams = new Type[] { Object.class };
}
}
Class<?> elementTypeClass = TypeManufacturerUtil.resolveGenericParameter(typeParams[0],
manufacturingCtx, elementGenericTypeArgs);
Type[] elementGenericArgs = ArrayUtils.addAll(
elementGenericTypeArgs.get(), genericTypeArgs);
elementGenericTypeArgs.set(elementGenericArgs);
return elementTypeClass;
}
/**
* It fills a collection with the required number of elements of the
* required type.
*
* <p>
* This method has a so-called side effect. It updates the collection passed
* as argument.
* </p>
*
* @param manufacturingCtx
* the manufacturing context
* @param annotations
* The annotations for this attribute
* @param attributeName
* The attribute name of collection in enclosing POJO class
* @param collection
* The Collection to be filled
* @param collectionElementType
* The type of the collection element
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*
*/
private void fillCollection(ManufacturingContext manufacturingCtx,
List<Annotation> annotations, String attributeName,
Collection<? super Object> collection,
Class<?> collectionElementType, Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
// If the user defined a strategy to fill the collection elements,
// we use it
Holder<AttributeStrategy<?>> elementStrategyHolder
= new Holder<AttributeStrategy<?>>();
Holder<AttributeStrategy<?>> keyStrategyHolder = null;
Integer nbrElements = TypeManufacturerUtil.findCollectionSize(strategy, annotations,
collectionElementType, elementStrategyHolder, keyStrategyHolder);
AttributeStrategy<?> elementStrategy = elementStrategyHolder.getValue();
try {
if (collection.size() > nbrElements) {
collection.clear();
}
for (int i = collection.size(); i < nbrElements; i++) {
// The default
Object element = TypeManufacturerUtil.returnAttributeDataStrategyValue(
collectionElementType, annotations, elementStrategy);
if (null == element) {
manufacturingCtx.backupTypeArgsMap(manufacturingCtx.createEmptyTypeArgsMap());
element = manufactureAttributeValue(collection, manufacturingCtx,
collectionElementType, collectionElementType,
annotations, attributeName, genericTypeArgs);
manufacturingCtx.restoreTypeArgsMap();
}
if (null != element) {
collection.add(element);
}
}
} catch (UnsupportedOperationException e) {
LOG.warn("Cannot fill immutable collection {}", collection.getClass());
}
}
/**
* It manufactures and returns a Map with at least one element in it
*
* @param pojo
* The POJO being initialized
* @param manufacturingCtx
* the manufacturing context
* @param attributeMetadata
* The attribute's metadata
* @return Map with at least one element in it
*
* @throws IllegalArgumentException
* <ul>
* <li>If the attribute name is null or empty</li>
* <li>If the array of types of the Map has length different
* from 2</li>
* </ul>
*
* @throws PodamMockeryException
* If an error occurred while creating the Map object
*/
private Map<? super Object, ? super Object> resolveMapValueWhenMapIsPojoAttribute(
Object pojo, ManufacturingContext manufacturingCtx,
AttributeMetadata attributeMetadata) {
String attributeName = attributeMetadata.getAttributeName();
Map<Object, Object> defaultValue = null;
if (null != pojo && !Character.isDigit(attributeName.charAt(0))) {
defaultValue = PodamUtils.getFieldValue(pojo, attributeName);
}
Map<Object, Object> retValue;
if (null != defaultValue &&
(defaultValue.getClass().getModifiers() & Modifier.PRIVATE) == 0) {
/* Default map, which is not immutable */
retValue = defaultValue;
} else {
@SuppressWarnings("unchecked")
Class<Map<Object,Object>> mapType
= (Class<Map<Object, Object>>) attributeMetadata.getAttributeType();
retValue = strategy.getTypeValue(attributeMetadata, manufacturingCtx, mapType);
if (null != retValue && null != defaultValue) {
retValue.putAll(defaultValue);
}
}
if (null == retValue) {
return null;
}
try {
Class<?> keyClass = null;
Class<?> elementClass = null;
AtomicReference<Type[]> keyGenericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
AtomicReference<Type[]> elementGenericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
if (ArrayUtils.isEmpty(attributeMetadata.getAttrGenericArgs())) {
MapArguments mapArgs = findInheretedMapElementType(retValue,
manufacturingCtx,
attributeMetadata.getAttrGenericArgs());
keyClass = mapArgs.getKeyOrValueType();
elementClass = mapArgs.getElementClass();
} else {
// Expected only key, value type
if (attributeMetadata.getAttrGenericArgs().length != 2) {
throw new IllegalStateException(
"In a Map only key value generic type are expected,"
+ "but received " + Arrays.toString(attributeMetadata.getAttrGenericArgs()));
}
Type[] actualTypeArguments = attributeMetadata.getAttrGenericArgs();
keyClass = TypeManufacturerUtil.resolveGenericParameter(actualTypeArguments[0],
manufacturingCtx, keyGenericTypeArgs);
elementClass = TypeManufacturerUtil.resolveGenericParameter(actualTypeArguments[1],
manufacturingCtx, elementGenericTypeArgs);
}
MapArguments mapArguments = new MapArguments();
mapArguments.setAttributeName(attributeName);
mapArguments.getAnnotations().addAll(attributeMetadata.getAttributeAnnotations());
mapArguments.setMapToBeFilled(retValue);
mapArguments.setKeyOrValueType(keyClass);
mapArguments.setElementClass(elementClass);
mapArguments.setKeyGenericTypeArgs(keyGenericTypeArgs.get());
mapArguments
.setElementGenericTypeArgs(elementGenericTypeArgs.get());
fillMap(mapArguments, manufacturingCtx);
} catch (InstantiationException e) {
throw new PodamMockeryException(MAP_CREATION_EXCEPTION_STR, e);
} catch (IllegalAccessException e) {
throw new PodamMockeryException(MAP_CREATION_EXCEPTION_STR, e);
} catch (SecurityException e) {
throw new PodamMockeryException(MAP_CREATION_EXCEPTION_STR, e);
} catch (ClassNotFoundException e) {
throw new PodamMockeryException(MAP_CREATION_EXCEPTION_STR, e);
} catch (InvocationTargetException e) {
throw new PodamMockeryException(MAP_CREATION_EXCEPTION_STR, e);
}
return retValue;
}
/**
* Finds key and element type arguments
*
* @param map
* The map being initialized
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
* @return
* Inherited map key and element types
*
*/
private MapArguments findInheretedMapElementType(Map<Object, Object> map,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs) {
Class<?> pojoClass = map.getClass();
Class<?> mapClass = pojoClass;
AtomicReference<Type[]> elementGenericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
Type[] typeParams = mapClass.getTypeParameters();
main : while (typeParams.length < 2) {
for (Type genericIface : mapClass.getGenericInterfaces()) {
Class<?> clazz = TypeManufacturerUtil.resolveGenericParameter(
genericIface, manufacturingCtx, elementGenericTypeArgs);
if (Map.class.isAssignableFrom(clazz)) {
typeParams = elementGenericTypeArgs.get();
mapClass = clazz;
continue main;
}
}
Type type = mapClass.getGenericSuperclass();
if (type != null) {
Class<?> clazz = TypeManufacturerUtil.resolveGenericParameter(
type, manufacturingCtx, elementGenericTypeArgs);
if (Map.class.isAssignableFrom(clazz)) {
typeParams = elementGenericTypeArgs.get();
mapClass = clazz;
continue main;
}
}
if (Map.class.equals(mapClass)) {
LOG.warn("Map {} doesn't have generic types,"
+ "will use Object, Object instead", pojoClass);
typeParams = new Type[] { Object.class, Object.class };
}
}
AtomicReference<Type[]> keyGenericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
Class<?> keyClass = TypeManufacturerUtil.resolveGenericParameter(typeParams[0],
manufacturingCtx, keyGenericTypeArgs);
Class<?> elementClass = TypeManufacturerUtil.resolveGenericParameter(
typeParams[1], manufacturingCtx, elementGenericTypeArgs);
Type[] keyGenericArgs = ArrayUtils.addAll(keyGenericTypeArgs.get(),
genericTypeArgs);
Type[] elementGenericArgs = ArrayUtils.addAll(elementGenericTypeArgs.get(),
genericTypeArgs);
MapArguments mapArguments = new MapArguments();
for (Annotation annotation : pojoClass.getAnnotations()) {
mapArguments.getAnnotations().add(annotation);
}
mapArguments.setMapToBeFilled(map);
mapArguments.setKeyOrValueType(keyClass);
mapArguments.setElementClass(elementClass);
mapArguments.setKeyGenericTypeArgs(keyGenericArgs);
mapArguments.setElementGenericTypeArgs(elementGenericArgs);
return mapArguments;
}
/**
* It fills a Map with the required number of elements of the required type.
*
* <p>
* This method has a so-called side-effect. It updates the Map given as
* argument.
* </p>
*
* @param mapArguments
* The arguments POJO
* @param manufacturingCtx
* Manufacturing context
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*
*/
private void fillMap(MapArguments mapArguments, ManufacturingContext manufacturingCtx)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
// If the user defined a strategy to fill the collection elements,
// we use it
Holder<AttributeStrategy<?>> elementStrategyHolder
= new Holder<AttributeStrategy<?>>();
Holder<AttributeStrategy<?>> keyStrategyHolder
= new Holder<AttributeStrategy<?>>();
Integer nbrElements = TypeManufacturerUtil.findCollectionSize(strategy, mapArguments.getAnnotations(),
mapArguments.getElementClass(), elementStrategyHolder,
keyStrategyHolder);
AttributeStrategy<?> keyStrategy = keyStrategyHolder.getValue();
AttributeStrategy<?> elementStrategy = elementStrategyHolder.getValue();
Map<? super Object, ? super Object> map = mapArguments.getMapToBeFilled();
try {
if (map.size() > nbrElements) {
map.clear();
}
for (int i = map.size(); i < nbrElements; i++) {
Object keyValue = null;
Object elementValue = null;
MapKeyOrElementsArguments valueArguments = new MapKeyOrElementsArguments();
valueArguments.setAttributeName(mapArguments.getAttributeName());
valueArguments.setMapToBeFilled(mapArguments.getMapToBeFilled());
valueArguments.getAnnotations().addAll(mapArguments.getAnnotations());
valueArguments.setKeyOrValueType(mapArguments.getKeyOrValueType());
valueArguments.setElementStrategy(keyStrategy);
valueArguments.setGenericTypeArgs(mapArguments
.getKeyGenericTypeArgs());
keyValue = getMapKeyOrElementValue(valueArguments, manufacturingCtx);
valueArguments.setKeyOrValueType(mapArguments.getElementClass());
valueArguments.setElementStrategy(elementStrategy);
valueArguments.setGenericTypeArgs(mapArguments
.getElementGenericTypeArgs());
elementValue = getMapKeyOrElementValue(valueArguments, manufacturingCtx);
if (elementValue != null) {
map.put(keyValue, elementValue);
}
}
} catch (UnsupportedOperationException e) {
LOG.warn("Cannot fill immutable map {}", map.getClass());
}
}
/**
* It fills a Map key or value with the appropriate value, considering
* attribute-level customisation.
*
* @param keyOrElementsArguments
* The arguments POJO
* @param manufacturingCtx
* manufacturing context
* @return A Map key or value
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws IllegalArgumentException
* <ul>
* <li>If an illegal argument was passed</li>
* <li>If an invalid value was set for a precise value in an
* annotation and such value could not be converted to the
* desired type</li>
* </ul>
* @throws ClassNotFoundException
* If manufactured class could not be loaded
*/
private Object getMapKeyOrElementValue(
MapKeyOrElementsArguments keyOrElementsArguments,
ManufacturingContext manufacturingCtx)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
AttributeStrategy<?> strategy = keyOrElementsArguments.getElementStrategy();
Object retValue = TypeManufacturerUtil.returnAttributeDataStrategyValue(
keyOrElementsArguments.getKeyOrValueType(),
keyOrElementsArguments.getAnnotations(),
strategy);
if (null == retValue) {
manufacturingCtx.backupTypeArgsMap(manufacturingCtx.createEmptyTypeArgsMap());
retValue = manufactureAttributeValue(
keyOrElementsArguments.getMapToBeFilled(),
manufacturingCtx,
keyOrElementsArguments.getKeyOrValueType(),
keyOrElementsArguments.getKeyOrValueType(),
keyOrElementsArguments.getAnnotations(),
keyOrElementsArguments.getAttributeName(),
keyOrElementsArguments.getGenericTypeArgs());
manufacturingCtx.restoreTypeArgsMap();
}
return retValue;
}
/**
* It fills an Array with the required number of elements of the required type.
*
* <p>
* This method has a so-called side-effect. It updates the Map given as
* argument.
* </p>
*
* @param array
* The array POJO
* @param attributeName
* The attribute name of collection in enclosing POJO class
* @param elementType
* The generic type of the collection element
* @param genericElementType
* The generic type of the collection element
* @param annotations
* The annotations for this attribute
* @param manufacturingCtx
* Manufacturing context
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*
*/
private void fillArray(Object array, String attributeName, Class<?> elementType,
Type genericElementType, List<Annotation> annotations,
ManufacturingContext manufacturingCtx)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Class<?> componentType = array.getClass().getComponentType();
Type genericComponentType;
AtomicReference<Type[]> genericTypeArgs = new AtomicReference<Type[]>(
PodamConstants.NO_TYPES);
if (genericElementType instanceof GenericArrayType) {
genericComponentType = ((GenericArrayType) genericElementType).getGenericComponentType();
if (genericComponentType instanceof TypeVariable) {
TypeVariable<?> componentTypeVariable
= (TypeVariable<?>) genericComponentType;
final Type resolvedType
= manufacturingCtx.resolveType(componentTypeVariable.getName());
componentType
= TypeManufacturerUtil.resolveGenericParameter(resolvedType,
manufacturingCtx, genericTypeArgs);
}
} else {
genericComponentType = componentType;
}
// If the user defined a strategy to fill the collection elements,
// we use it
Holder<AttributeStrategy<?>> elementStrategyHolder
= new Holder<AttributeStrategy<?>>();
Holder<AttributeStrategy<?>> keyStrategyHolder = null;
TypeManufacturerUtil.findCollectionSize(strategy,
annotations, elementType,
elementStrategyHolder, keyStrategyHolder);
AttributeStrategy<?> elementStrategy = elementStrategyHolder.getValue();
int nbrElements = Array.getLength(array);
for (int i = 0; i < nbrElements; i++) {
Object arrayElement = Array.get(array, i);
if (null == arrayElement || arrayElement.getClass().isPrimitive() || arrayElement instanceof Number) {
// The default
arrayElement = TypeManufacturerUtil.returnAttributeDataStrategyValue(
componentType, annotations, elementStrategy);
if (null == arrayElement) {
arrayElement = manufactureAttributeValue(array, manufacturingCtx,
componentType, genericComponentType,
annotations, attributeName,
genericTypeArgs.get());
}
Array.set(array, i, arrayElement);
}
}
}
/**
* It returns an Array with the first element set
*
*
* @param pojo
* POJO containing attribute
* @param manufacturingCtx
* the manufacturing context
* @param attributeMetadata
* The attribute's metadata
* @return Array with the first element set
* @throws IllegalArgumentException
* If an illegal argument was passed to the constructor
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*/
private Object resolveArrayElementValue(Object pojo,
ManufacturingContext manufacturingCtx,
AttributeMetadata attributeMetadata) throws InstantiationException,
IllegalAccessException, InvocationTargetException,
ClassNotFoundException {
@SuppressWarnings("unchecked")
Class<Object> arrayType
= (Class<Object>) attributeMetadata.getAttributeType();
Object array = strategy.getTypeValue(attributeMetadata, manufacturingCtx, arrayType);
fillArray(array, attributeMetadata.getAttributeName(),
attributeMetadata.getAttributeType(),
attributeMetadata.getAttributeGenericType(),
attributeMetadata.getAttributeAnnotations(),
manufacturingCtx);
return array;
}
/**
* Given a constructor it manufactures and returns the parameter values
* required to invoke it
*
* @param constructor
* The constructor for which parameter values are required
* @param pojoClass
* The POJO class containing the constructor
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
*
* @return The parameter values required to invoke the constructor
* @throws IllegalArgumentException
* If an illegal argument was passed to the constructor
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*/
private Object[] getParameterValuesForConstructor(
Constructor<?> constructor, Class<?> pojoClass,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Class<?>[] parameterTypes = constructor.getParameterTypes();
if (parameterTypes.length == 0) {
return PodamConstants.NO_ARGS;
} else {
Object[] parameterValues = new Object[parameterTypes.length];
Annotation[][] parameterAnnotations = constructor.getParameterAnnotations();
Type[] genericTypes = constructor.getGenericParameterTypes();
String ctorName = Arrays.toString(genericTypes);
for (int idx = 0; idx < parameterTypes.length; idx++) {
List<Annotation> annotations = Arrays
.asList(parameterAnnotations[idx]);
Type genericType = (idx < genericTypes.length) ?
genericTypes[idx] : parameterTypes[idx];
parameterValues[idx] = manufactureParameterValue(pojoClass,
idx + ctorName, parameterTypes[idx], genericType,
annotations, manufacturingCtx, genericTypeArgs);
}
return parameterValues;
}
}
/**
* Given a method it manufactures and returns the parameter values
* required to invoke it
*
* @param method
* The method for which parameter values are required
* @param pojoClass
* The POJO class containing the constructor
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
*
* @return The parameter values required to invoke the method
* @throws IllegalArgumentException
* If an illegal argument was passed to the method
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*/
private Object[] getParameterValuesForMethod(
Method method, Class<?> pojoClass,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Class<?>[] parameterTypes = method.getParameterTypes();
if (parameterTypes.length == 0) {
return PodamConstants.NO_ARGS;
} else {
Object[] parameterValues = new Object[parameterTypes.length];
Annotation[][] parameterAnnotations = method.getParameterAnnotations();
Type[] genericTypes = method.getGenericParameterTypes();
String methodName = Arrays.toString(genericTypes);
for (int idx = 0; idx < parameterTypes.length; idx++) {
List<Annotation> annotations = Arrays
.asList(parameterAnnotations[idx]);
Type genericType = (idx < genericTypes.length) ?
genericTypes[idx] : parameterTypes[idx];
parameterValues[idx] = manufactureParameterValue(pojoClass,
idx + methodName, parameterTypes[idx], genericType,
annotations, manufacturingCtx, genericTypeArgs);
}
return parameterValues;
}
}
/**
* Manufactures and returns the parameter value for method required to
* invoke it
*
* @param pojoClass pojo class
* @param parameterName name of parameter
* @param parameterType type of parameter
* @param genericType generic type of parameter
* @param annotations parameter annotations
* @param manufacturingCtx
* the manufacturing context
* @param genericTypeArgs
* The generic type arguments for the current generic class
* instance
*
* @return The parameter values required to invoke the constructor
* @throws IllegalArgumentException
* If an illegal argument was passed to the constructor
* @throws InstantiationException
* If an exception occurred during instantiation
* @throws IllegalAccessException
* If security was violated while creating the object
* @throws InvocationTargetException
* If an exception occurred while invoking the constructor or
* factory method
* @throws ClassNotFoundException
* If it was not possible to create a class from a string
*/
private Object manufactureParameterValue(Class<?> pojoClass,
String parameterName, Class<?> parameterType, Type genericType,
final List<Annotation> annotations,
ManufacturingContext manufacturingCtx,
Type... genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
AttributeStrategy<?> attributeStrategy
= TypeManufacturerUtil.findAttributeStrategy(strategy, annotations, parameterType);
if (null != attributeStrategy) {
return TypeManufacturerUtil.returnAttributeDataStrategyValue(
parameterType, annotations, attributeStrategy);
}
final boolean cloneTypeArgsMap = (genericType instanceof ParameterizedType);
Type[] genericTypeArgsAll;
if (cloneTypeArgsMap) {
genericTypeArgsAll = manufacturingCtx.cloneTypeArgsMap(
parameterType, (ParameterizedType) genericType, genericTypeArgs);
} else {
genericTypeArgsAll = genericTypeArgs;
}
Object retValue = manufactureAttributeValue(pojoClass, manufacturingCtx, parameterType,
genericType, annotations, parameterName,
genericTypeArgsAll);
if (cloneTypeArgsMap) {
manufacturingCtx.restoreTypeArgsMap();
}
return retValue;
}
/**
* Returns a value for an abstract type or interface if possible.
* @param pojoClass The Pojo class
* @param pojoMetadata The Pojo metadata
* @param manufacturingCtx The manufacturing context
* @param genericTypeArgs The generic type arguments map
* @param <T> The type of the value to be returned
* @return a value or null, if manufacturing didn't succeed
* @throws InstantiationException If a problem occurred while instantiating the object
* @throws IllegalAccessException If a problem occurred while instantiating the object
* @throws InvocationTargetException If a problem occurred while instantiating the object
* @throws ClassNotFoundException If a problem occurred while instantiating the object
*/
private <T> T getValueForAbstractType(Class<T> pojoClass,
AttributeMetadata pojoMetadata,
ManufacturingContext manufacturingCtx,
Type[] genericTypeArgs)
throws InstantiationException, IllegalAccessException,
InvocationTargetException, ClassNotFoundException {
Class<? extends T> specificClass = strategy.getSpecificClass(pojoClass);
if (!specificClass.equals(pojoClass)) {
return this.manufacturePojoInternal(specificClass, pojoMetadata,
manufacturingCtx, genericTypeArgs);
}
Class<?> factory = strategy.getFactoryClass(pojoClass);
if (factory != null) {
T retValue = instantiatePojoWithFactory(factory, pojoClass,
manufacturingCtx, genericTypeArgs);
if (retValue != null) {
return retValue;
}
}
return resortToExternalFactory(manufacturingCtx,
"Cannot instantiate a class {}. Resorting to {} external factory",
pojoClass, genericTypeArgs);
}
}