
tech.sirwellington.alchemy.generator.ObjectGenerators Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of alchemy-generator Show documentation
Show all versions of alchemy-generator Show documentation
Part of the Alchemy collection.
More Data means better tests.
Alchemy Generator makes it easier to test your code
by providing generators for common Objects and Data.
/*
* Copyright 2015 SirWellington Tech.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package tech.sirwellington.alchemy.generator;
import java.lang.reflect.Constructor;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Modifier;
import java.lang.reflect.ParameterizedType;
import java.lang.reflect.Type;
import java.net.URL;
import java.nio.ByteBuffer;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
import java.util.stream.Collectors;
import org.apache.commons.lang3.ClassUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import tech.sirwellington.alchemy.annotations.access.NonInstantiable;
import tech.sirwellington.alchemy.annotations.designs.patterns.StrategyPattern;
import static tech.sirwellington.alchemy.annotations.designs.patterns.StrategyPattern.Role.CONCRETE_BEHAVIOR;
import static tech.sirwellington.alchemy.generator.AlchemyGenerator.one;
import static tech.sirwellington.alchemy.generator.Checks.checkNotNull;
import static tech.sirwellington.alchemy.generator.Checks.checkThat;
import static tech.sirwellington.alchemy.generator.NumberGenerators.integers;
/**
* Contains Convenience Generators for POJOs (Plain-Old-Java-Objects).
* Use at your own risk.
*
* @author SirWellington
*/
@NonInstantiable
@StrategyPattern(role = CONCRETE_BEHAVIOR)
public final class ObjectGenerators
{
private final static Logger LOG = LoggerFactory.getLogger(ObjectGenerators.class);
private static final Map, AlchemyGenerator>> DEFAULT_GENERATOR_MAPPINGS = new ConcurrentHashMap<>();
static
{
DEFAULT_GENERATOR_MAPPINGS.put(String.class, StringGenerators.alphabeticString());
DEFAULT_GENERATOR_MAPPINGS.put(Integer.class, NumberGenerators.smallPositiveIntegers());
DEFAULT_GENERATOR_MAPPINGS.put(Long.class, NumberGenerators.positiveLongs());
DEFAULT_GENERATOR_MAPPINGS.put(Double.class, NumberGenerators.positiveDoubles());
DEFAULT_GENERATOR_MAPPINGS.put(Date.class, DateGenerators.anyTime());
DEFAULT_GENERATOR_MAPPINGS.put(Instant.class, TimeGenerators.anytime());
DEFAULT_GENERATOR_MAPPINGS.put(ByteBuffer.class, BinaryGenerators.byteBuffers(333));
DEFAULT_GENERATOR_MAPPINGS.put(Boolean.class, BooleanGenerators.booleans());
DEFAULT_GENERATOR_MAPPINGS.put(Byte.class, BinaryGenerators.bytes());
DEFAULT_GENERATOR_MAPPINGS.put(URL.class, NetworkGenerators.httpUrls());
}
ObjectGenerators() throws IllegalAccessException
{
throw new IllegalAccessException("cannot instantiate this class");
}
/**
* Use at your own risk! This {@link AlchemyGenerator } Inflates a Basic POJO
* Object with randomly generated values. Do not use this to generate Primitive types;
* use instead the Alchemy Generators carefully designed and crafted for Primitives.
*
* The basic rules for the POJO are the following.
* Each field must be:
*
* - Non-Static
*
- Non-Final
*
- Primitive type: Integer, Double, etc
*
- {@link String} Type
*
- {@linkplain Enum enum} Type
*
- {@link Date} Type
*
- {@link Instant} Type
*
- Another {@code POJO} That satisfies these rules (embedded Object)
*
- Non-Circular (Cannot contain circular references). A Stack Overflow will occur otherwise.
*
- A {@link List} with a Type Parameter matching the above.
*
- A {@link Set} with a Type Parameter matching the above.
*
- A {@link Map} with Type Parameters matching the above conditions.
*
*
* Valid Examples:
*
* {@code
*
* private class Computer
* {
* private Date releaseDate;
* private String name;
* private String manufacturer;
* private double cost;
* }
*
* private class Person
* {
* private String name;
* private int age;
* private double money;
* private Computer computer;
* }
*
* private class Company
* {
*
* private String name;
* private int numberOfEmployees;
* private List employees;
* }
*
* private class CompanyIndex
* {
* private String indexName;
* private Map index;
* }
* }
*
* @param
* @param classOfPojo
* @return
* @see StringGenerators
* @see NumberGenerators
* @see DateGenerators
* @see TimeGenerators
*/
public static AlchemyGenerator pojos(Class classOfPojo)
{
return pojos(classOfPojo, DEFAULT_GENERATOR_MAPPINGS);
}
public static AlchemyGenerator pojos(Class classOfPojo, Map, AlchemyGenerator>> customMappings)
{
checkNotNull(classOfPojo, "missing class of POJO");
checkThat(canInstantiate(classOfPojo),
"cannot instantiate class: " + classOfPojo);
checkThat(!isPrimitiveClass(classOfPojo),
"Cannot use pojos with Primitive Type. Use one of the Primitive generators instead.");
List validFields = Arrays.asList(classOfPojo.getDeclaredFields())
.stream()
.filter(f -> !isStatic(f))
.filter(f -> !isFinal(f))
.collect(Collectors.toList());
return () ->
{
T instance;
try
{
instance = instantiate(classOfPojo);
}
catch (Exception ex)
{
LOG.error("Failed to instantiate {}", classOfPojo.getName(), ex);
return null;
}
for (Field field : validFields)
{
tryInjectField(instance, field, customMappings);
}
return instance;
};
}
private static boolean canInstantiate(Class classOfPojo)
{
try
{
instantiate(classOfPojo);
return true;
}
catch (Exception ex)
{
LOG.warn("cannot instatiate type {}", classOfPojo);
return false;
}
}
private static boolean isStatic(Field field)
{
int modifiers = field.getModifiers();
return Modifier.isStatic(modifiers);
}
private static boolean isFinal(Field field)
{
int modifiers = field.getModifiers();
return Modifier.isFinal(modifiers);
}
private static T instantiate(Class classOfT) throws NoSuchMethodException, InstantiationException, IllegalAccessException, IllegalArgumentException, InvocationTargetException
{
Constructor defaultConstructor = classOfT.getDeclaredConstructor();
boolean originalAccessibility = defaultConstructor.isAccessible();
try
{
defaultConstructor.setAccessible(true);
return defaultConstructor.newInstance();
}
finally
{
defaultConstructor.setAccessible(originalAccessibility);
}
}
private static boolean isPrimitiveClass(Class classOfPojo)
{
if (classOfPojo.isPrimitive())
{
return true;
}
Set otherPrimitives = new HashSet<>();
otherPrimitives.add(String.class);
otherPrimitives.add(Date.class);
otherPrimitives.add(Instant.class);
if (otherPrimitives.contains(classOfPojo))
{
return true;
}
return false;
}
private static void tryInjectField(T instance, Field field, Map, AlchemyGenerator>> generatorMappings)
{
try
{
injectField(instance, field, generatorMappings);
}
catch (IllegalAccessException | IllegalArgumentException ex)
{
LOG.warn("Could not inject field {}", field.toString(), ex);
}
}
private static void injectField(Object pojo, Field field, Map, AlchemyGenerator>> generatorMappings) throws IllegalArgumentException, IllegalAccessException
{
Class> typeOfField = field.getType();
typeOfField = ClassUtils.primitiveToWrapper(typeOfField);
AlchemyGenerator> generator = determineGeneratorFor(typeOfField, field, generatorMappings);
if (generator == null)
{
LOG.warn("Could not find a suitable AlchemyGenerator for field {} with type {}", field, typeOfField);
return;
}
Object value = generator.get();
boolean originalAccessibility = field.isAccessible();
try
{
field.setAccessible(true);
field.set(pojo, value);
}
finally
{
field.setAccessible(originalAccessibility);
}
}
private static AlchemyGenerator> determineGeneratorFor(Class> typeOfField, Field field, Map, AlchemyGenerator>> generatorMappings)
{
AlchemyGenerator> generator = generatorMappings.get(typeOfField);
if (generator != null)
{
//Already found it
return generator;
}
if (isCollectionType(typeOfField))
{
if (lacksGenericTypeArguments(field))
{
LOG.warn("POJO {} contains a Collection field {} which is not type-parametrized. Cannot inject.",
field.getDeclaringClass(),
field);
return null;
}
generator = determineGeneratorForCollectionField(field, typeOfField, generatorMappings);
}
else if (isEnumType(typeOfField))
{
Object[] enumValues = typeOfField.getEnumConstants();
if (enumValues == null)
{
LOG.warn("Enum Class {} has no Enum Values: " + typeOfField);
return null;
}
generator = () ->
{
int position = one(integers(0, enumValues.length));
return enumValues[position];
};
}
else
{
//Assume Pojo and recurse
generator = pojos(typeOfField);
}
return generator;
}
private static boolean isCollectionType(Class> type)
{
return isListType(type) ||
isSetType(type) ||
isMapType(type);
}
private static boolean isListType(Class> type)
{
return List.class.isAssignableFrom(type);
}
private static boolean isSetType(Class> type)
{
return Set.class.isAssignableFrom(type);
}
private static boolean isMapType(Class> type)
{
return Map.class.isAssignableFrom(type);
}
private static boolean lacksGenericTypeArguments(Field field)
{
Type genericType = field.getGenericType();
return !(genericType instanceof ParameterizedType);
}
private static AlchemyGenerator> determineGeneratorForCollectionField(Field collectionField,
Class> collectionType,
Map, AlchemyGenerator>> generatorMappings)
{
if (isMapType(collectionType))
{
return determineGeneratorForMapField(collectionField, collectionType, generatorMappings);
}
ParameterizedType parameterizedType = (ParameterizedType) collectionField.getGenericType();
Class> valueType = (Class>) parameterizedType.getActualTypeArguments()[0];
AlchemyGenerator> generator = generatorMappings.getOrDefault(valueType,
determineGeneratorFor(valueType, collectionField, generatorMappings));
List
© 2015 - 2025 Weber Informatics LLC | Privacy Policy