fr.vergne.pester.definition.PojoDefinition Maven / Gradle / Ivy
Show all versions of pester-core Show documentation
package fr.vergne.pester.definition;
import java.util.Arrays;
import java.util.Collection;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Optional;
import java.util.function.Supplier;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import fr.vergne.pester.factory.Factory;
import fr.vergne.pester.junit.annotation.ConstructorPropertiesHelper;
import fr.vergne.pester.options.Visibility;
import fr.vergne.pester.util.cache.Cache;
import fr.vergne.pester.util.cache.Key;
import fr.vergne.pester.util.cache.ParameteredKey;
import fr.vergne.pester.util.observer.OccurrenceObserver;
import fr.vergne.pester.value.Generator;
import fr.vergne.pester.value.Type;
public class PojoDefinition {
private final Factory factory;
private final Class
pojoClass;
private Optional> parentClass = Optional.empty();
private final Collection> interfaces = new LinkedHashSet<>();
private final Optional> pojoGenerator;
private final Map> properties;
private final Map>, ConstructorDefinition> constructors;
/** Used only if no generator is set and no valid constructor is found */
private final NullPointerException nullPojoGeneratorException;
private final Cache cache = Cache.create();
public PojoDefinition(Class
pojoClass) {
this(pojoClass, new Factory());
}
public PojoDefinition(Class
pojoClass, Factory factory) {
this(pojoClass, Optional.empty(), factory);
}
public PojoDefinition(Generator
pojoGenerator) {
this(pojoGenerator, new Factory());
}
@SuppressWarnings("unchecked")
public PojoDefinition(Generator
pojoGenerator, Factory factory) {
this((Class
) pojoGenerator.create().getClass(), Optional.of(pojoGenerator), factory);
}
public PojoDefinition(Class
pojoClass, Generator
pojoGenerator) {
this(pojoClass, Optional.of(pojoGenerator), new Factory());
}
private PojoDefinition(Class
pojoClass, Optional> optional, Factory factory) {
this.pojoClass = pojoClass;
this.properties = new LinkedHashMap<>();
this.constructors = new LinkedHashMap<>();
this.pojoGenerator = optional;
this.nullPojoGeneratorException = new NullPointerException("No Pojo generator set, set one or add constructors");
this.factory = factory;
}
public Factory create() {
return factory;
}
// POJO
public Class getPojoClass() {
return pojoClass;
}
public void setParentClass(Class> parentClass) {
if (parentClass.isInterface()) {
throw new InterfaceClassException(parentClass);
} else {
this.parentClass = Optional.of(parentClass);
}
}
public Optional> getParentClass() {
return parentClass;
}
public void addInterfaces(Class>... interfaceClasses) {
Optional> nonInterfaceClass = Stream.of(interfaceClasses)
.filter(interfaceClass -> !interfaceClass.isInterface())
.findAny();
if (nonInterfaceClass.isPresent()) {
throw new NonInterfaceClassException(nonInterfaceClass.get());
} else {
interfaces.addAll(Arrays.asList(interfaceClasses));
}
}
public Collection> getInterfaces() {
return interfaces;
}
private final Key> pojoGeneratorKey = ParameteredKey.create(new Object());
public Generator getPojoGenerator() {
return pojoGenerator.orElseGet(() -> cache.get(pojoGeneratorKey, this::createPojoGeneratorFromConstructors));
}
private Generator
createPojoGeneratorFromConstructors() {
return () -> {
OccurrenceObserver generatorException = new OccurrenceObserver<>();
OccurrenceObserver invalidConstructorException = new OccurrenceObserver<>();
Optional pojo = getConstructors().stream()
.sorted(Comparator.comparing(constructor -> constructor.getParametersDefinitions().size()))
.map(definition -> {
List> parameters;
try {
parameters = definition.getParametersGenerator().create();
} catch (Exception cause) {
generatorException.occurs(cause);
return null;
}
fr.vergne.pester.model.Constructor
constructor = definition.getInstance();
try {
return constructor.invoke(parameters);
} catch (Exception cause) {
invalidConstructorException.occurs(new ConstructorCannotGeneratePojoException(cause));
return null;
}
})
.filter(Objects::nonNull)
.findAny();
if (pojo.isPresent()) {
return pojo.get();
} else if (generatorException.hasOccurred()) {
throw new PojoGeneratorNotFoundException(generatorException.getFirstOccurrence());
} else if (invalidConstructorException.hasOccurred()) {
throw new PojoGeneratorNotFoundException(invalidConstructorException.getFirstOccurrence());
} else {
throw new PojoGeneratorNotFoundException(nullPojoGeneratorException);
}
};
}
// CONSTRUCTORS
@SafeVarargs
public final ConstructorDefinition
addConstructor(Visibility visibility, PropertyDefinition
... parameters) {
return addConstructor(Optional.of(visibility), parameters);
}
@SafeVarargs
public final ConstructorDefinition
addConstructor(PropertyDefinition
... parameters) {
return addConstructor(Optional.empty(), parameters);
}
@SafeVarargs
private final ConstructorDefinition
addConstructor(Optional visibility,
PropertyDefinition... parameters) {
List> params = Arrays.asList(parameters);
List> types = params.stream().map(PropertyDefinition::getType).collect(Collectors.toList());
return addConstructor(visibility, params, types);
}
private ConstructorDefinition addConstructor(Optional visibility, List> params,
List> types) {
return (ConstructorDefinition) constructors.compute(types, (k, previousDefinition) -> {
if (previousDefinition == null) {
return new ConstructorDefinition<>(pojoClass, params, visibility);
} else {
throw new IllegalArgumentException("Already defined constructor on " + types);
}
});
}
public Collection> getConstructors() {
return constructors.values();
}
// PROPERTIES
public PropertyDefinition addProperty(Class typeClass) {
return addProperty(factory.type().from(typeClass));
}
public PropertyDefinition addProperty(Type type) {
return addProperty(type, Optional.empty());
}
PropertyDefinition addProperty(Class typeClass, Optional name) {
return addProperty(factory.type().from(typeClass), name);
}
private long unnamedFieldIndex = 0;
private final Supplier autoFieldNamer = () -> "?"+(++unnamedFieldIndex);
PropertyDefinition addProperty(Type type, Optional name) {
return addProperty(type, name.orElseGet(autoFieldNamer));
}
public PropertyDefinition addProperty(Class typeClass, String name) {
return addProperty(factory.type().from(typeClass), name);
}
@SuppressWarnings("unchecked")
public PropertyDefinition addProperty(Type type, String name) {
if (name.equals(ConstructorPropertiesHelper.NON_PROPERTY)) {
return addProperty(type);
} else {
return (PropertyDefinition) properties.compute(name, (k, previousDefinition) -> {
if (previousDefinition == null) {
return new PropertyDefinition<>(pojoClass, name, type);
} else {
throw new AlreadyDefinedPropertyException(name);
}
});
}
}
public Collection> getProperties() {
return properties.values();
}
@SuppressWarnings("serial")
private static class PojoGeneratorNotFoundException extends RuntimeException {
public PojoGeneratorNotFoundException(Throwable cause) {
super("No valid POJO generator found", cause);
}
}
@SuppressWarnings("serial")
private static class ConstructorCannotGeneratePojoException extends RuntimeException {
public ConstructorCannotGeneratePojoException(Throwable cause) {
super("Cannot use constructor as POJO generator", cause);
}
}
@SuppressWarnings("serial")
private static class InterfaceClassException extends RuntimeException {
public InterfaceClassException(Class> interfaceClass) {
super("Not a class, but an interface: " + interfaceClass);
}
}
@SuppressWarnings("serial")
private static class NonInterfaceClassException extends RuntimeException {
public NonInterfaceClassException(Class> nonInterfaceClass) {
super("Not an interface: " + nonInterfaceClass);
}
}
}