public class Configuration{

    private boolean useFieldByName = false;
    private boolean overwriteAllAttributes = false;
    private boolean failOnMissingGenerators = true;
    private boolean failOnBoundaryValuesWithMissingGenerators = true;
    private Set<AttributeId> attributeIdsToOverwrite = new HashSet<>();

    public Configuration useVariableNamesAsAttributeId() {
        this.useFieldByName = true;
        return this;
    }

    public Configuration overwriteAllAttributes() {
        this.overwriteAllAttributes = true;
        return this;
    }

    public Configuration overwriteAttribute(String... attributeId){
        attributeIdsToOverwrite.addAll(
                Arrays.stream(attributeId).map(s -> new AttributeId(s)).collect(Collectors.toList())
        );
        return this;
    }

    public Configuration doNotFailOnMissingGenerators() {
        this.failOnMissingGenerators = false;
        return this;
    }

    public Configuration doNotFailOnMissingGeneratorsForBoundaryValue() {
        this.failOnBoundaryValuesWithMissingGenerators = false;
        return this;
    }

    protected boolean useFieldByName() {
        return useFieldByName;
    }

    protected boolean overwriteAttributesWithValues() {
        return overwriteAllAttributes;
    }

    protected boolean failOnMissingGenerators(){
        return failOnMissingGenerators;
    }

    protected boolean failOnBoundaryValuesWithMissingGenerators(){
        return failOnBoundaryValuesWithMissingGenerators;
    }

    protected boolean shouldAttributeBeOverwritten(AttributeId attributeId){
        return attributeIdsToOverwrite.contains(attributeId);
    }

    protected Configuration getCopyOfConfiguration(){
        return new Configuration(useFieldByName, overwriteAllAttributes, failOnMissingGenerators, failOnBoundaryValuesWithMissingGenerators, attributeIdsToOverwrite);
    }

    protected Configuration(){
    }

    private Configuration(boolean useFieldByName, boolean overwriteAllAttributes, boolean failOnMissingGenerators, boolean failOnBoundaryValuesWithMissingGenerators, Set<AttributeId> attributeIdsToOverwrite) {
        this.useFieldByName = useFieldByName;
        this.overwriteAllAttributes = overwriteAllAttributes;
        this.failOnMissingGenerators = failOnMissingGenerators;
        this.failOnBoundaryValuesWithMissingGenerators = failOnBoundaryValuesWithMissingGenerators;

        this.attributeIdsToOverwrite = attributeIdsToOverwrite.stream()
                .map(a -> new AttributeId(a.getAttributeId()))
                .collect(Collectors.toSet());
    }
} public class DataCow<T> {

    private T theDairy;
    private static Configuration staticConfig = new Configuration();
    private final Configuration config = staticConfig.getCopyOfConfiguration();
    private final Class<T> clazz;
    private Map<AttributeId, DataGenerator> generators = new HashMap<>();
    private Map<AttributeId, DataField> dataFields = new HashMap<>();

    private final Set<Class> withGenerators = new HashSet<>();
    private final Set<Object> withGeneratorInstances = new HashSet<>();
    private final Set<AttributeId> handledFieldAttributeIds = new HashSet<>();

    private DataCow(Class<T> clazz) {
        this.clazz = clazz;
    }

    public static <T> DataCow<T> generateDairyFor(Class<T> clazz) {

        DataCow<T> theCow = new DataCow<>(clazz);
        try {
            theCow.theDairy = clazz.newInstance();
        } catch (Exception e) {
            throw new DairyInstantiationException(String.format("Class %s can not be instantiated, does it have a no args constructor?", clazz.getName()), e);
        }

        return theCow;
    }

    public static <T> DataCow<T> generateDairyForInstance(T object) {
        DataCow<T> theCow = new DataCow(object.getClass());
        theCow.theDairy = object;
        return theCow;
    }

    public static void withStaticConfiguration(DataCowConfiguration config) {
        config.getConfiguration(staticConfig);
    }

    public static void useDefaultConfiguration() {
        staticConfig = new Configuration();
    }

    public DataCow<T> with(With<T> with) {
        with.mutateObject(theDairy);
        return this;
    }

    public DataCow<T> withGenerator(Class clazz) {
        this.withGenerators.add(clazz);
        return this;
    }

    public DataCow<T> withGenerator(Object generator) {
        this.withGeneratorInstances.add(generator);
        return this;
    }

    public DataCow<T> withConfiguration(DataCowConfiguration config) {
        config.getConfiguration(this.config);
        return this;
    }

    public T milkCow() {

        generators = collectGenerators();
        dataFields = collectFields();
        checkThatAllFieldsHaveGenerators(generators, dataFields);
        dataFields.values().stream().sorted().forEach(field -> milkFieldAndParentsRecursively(field));
        return theDairy;
    }

    private Map<AttributeId, DataGenerator> collectGenerators() {

        if (clazz.getAnnotation(WithGenerators.class) != null) {
            withGenerators.addAll(Arrays.asList(clazz.getAnnotation(WithGenerators.class).value()));
        }

        if (withGenerators.isEmpty() && withGeneratorInstances.isEmpty()) {
            throw new GeneratorNotFoundException("@WithGenerators was not specified in class " + clazz.getName());
        }

        Map<Class, Object> generatorInstanceObjects = collectGeneratorInstanceObjects();
        return collectDataGeneratorsFromGeneratorInstances(generatorInstanceObjects);
    }

    private Map<Class, Object> collectGeneratorInstanceObjects() {
        Map<Class, Object> generatorInstanceObjects = new HashMap<>();

        generatorInstanceObjects.putAll(withGeneratorInstances.stream()
                .collect(
                        Collectors.toMap(
                                generatorInstance -> generatorInstance.getClass(),
                                generatorInstance -> generatorInstance
                        )
                )
        );

        // Only instantiate generator classes that are not already passed as instances
        generatorInstanceObjects.putAll(withGenerators.stream()
                .filter(aClass -> !generatorInstanceObjects.containsKey(aClass))
                .collect(
                        Collectors.toMap(
                                aClass -> aClass,
                                aClass -> {
                                    try {
                                        return aClass.newInstance();
                                    } catch (InstantiationException | IllegalAccessException e) {
                                        throw new GeneratorCouldNotBeInstantiatedException(
                                                String.format(
                                                        "Generator class %s could not be instantiated, does it have a public no-args constructor?",
                                                        aClass.getName()
                                                ),
                                                e
                                        );
                                    }
                                }
                        )
                )
        );

        return generatorInstanceObjects;
    }

    private Map<AttributeId, DataGenerator> collectDataGeneratorsFromGeneratorInstances(Map<Class, Object> generatorInstanceObjects) {
        Map<AttributeId, DataGenerator> generators = new HashMap<>();

        for (Class generatorClass : generatorInstanceObjects.keySet()) {
            Arrays.stream(generatorClass.getMethods())
                    .filter(method -> method.isAnnotationPresent(Generator.class))
                    .map(method -> DataGenerator.builder()
                            .setAttributeId(new AttributeId(method.getDeclaredAnnotation(Generator.class).value()))
                            .setMethod(method)
                            .setObject(generatorInstanceObjects.get(generatorClass))
                            .build()
                    ).forEach(dataGenerator -> {
                        if (generators.containsKey(dataGenerator.getAttributeId())) {
                            throw new DuplicateAttributeException(
                                    String.format(
                                            "Duplicate attribute ids found in generators %s and %s",
                                            generators.get(dataGenerator.getAttributeId()).getQualifiedMethodName(),
                                            dataGenerator.getQualifiedMethodName()
                                    )
                            );
                        }
                        generators.put(dataGenerator.getAttributeId(), dataGenerator);
                    }
            );
        }

        return generators;
    }

    private Map<AttributeId, DataField> collectFields() {
        return getAllFields(theDairy.getClass()).stream()
                .filter(field -> field.isAnnotationPresent(Attribute.class) || config.useFieldByName())
                .map(field -> new DataField(
                                new AttributeId(
                                        field.isAnnotationPresent(Attribute.class)
                                                ? field.getAnnotationsByType(Attribute.class)[0].value()
                                                : field.getName()
                                ),
                                theDairy,
                                field,
                                field.isAnnotationPresent(Attribute.class) ? annotatedField : namedField
                        )
                )
                .collect(
                        Collectors.toMap(
                                field -> field.getAttributeId(),
                                field -> field,
                                (field1, field2) -> getFieldWithHighestPrio(field1, field2)
                        )
                );
    }

    private List<Field> getAllFields(Class clazz) {
        List<Field> fields = new ArrayList<>();
        fields.addAll(Arrays.asList(clazz.getDeclaredFields()));
        Class superClass = clazz.getSuperclass();
        if (superClass != null) {
            fields.addAll(getAllFields(superClass));
        }
        return fields;
    }

    private DataField getFieldWithHighestPrio(DataField field1, DataField field2) {
        if (field1.getFieldCategory().equals(field2.getFieldCategory())) {
            throw new DuplicateAttributeException(String.format("Duplicate attribute ids found in attributes %s and %s", field1.getQualifiedFieldName(), field2.getQualifiedFieldName()));
        } else if (annotatedField.equals(field1.getFieldCategory())) {
            return field1;
        } else {
            return field2;
        }
    }

    private void checkThatAllFieldsHaveGenerators(Map<AttributeId, DataGenerator> generators, Map<AttributeId, DataField> fields) {

        fields.forEach(
                (attributeId, dataField) -> {
                    if (!generators.containsKey(attributeId)) {
                        if (!config.failOnBoundaryValuesWithMissingGenerators() && dataField.getFieldValue() != null) {
                            return;
                        } else if (!config.failOnMissingGenerators()) {
                            return;
                        } else {
                            throw new GeneratorNotFoundException(
                                    String.format(
                                            "No generator was found for the following field: %s",
                                            dataField.getAttributeId()
                                    )
                            );
                        }
                    }
                }
        );
    }

    private Object milkFieldAndParentsRecursively(DataField field) {

        if (handledFieldAttributeIds.contains(field.getAttributeId())) {
            // We've already been here. Don't do anything but return the value (which can be null)!
            return field.getFieldValue();
        }

        handledFieldAttributeIds.add(field.getAttributeId());

        if (
                field.getFieldValue() != null &&
                        !config.overwriteAttributesWithValues() &&
                        !config.shouldAttributeBeOverwritten(field.getAttributeId())) {
            // If the field has a value already, don't create a new one
            return field.getFieldValue();
        }

        DataGenerator generator = generators.get(field.getAttributeId());

        if (generator == null) {
            return null;
        } else {
            Object[] parameterValues = generator.getParentFieldAttributeIds().stream()
                    .map(attributeId -> Optional.ofNullable(dataFields.get(attributeId))
                            .orElseThrow(
                                    () -> new UnknownGeneratorReferenceException(String.format("Attribute %s referenced from a generator could not be found", attributeId))
                            )
                    )
                    .map(parentField -> milkFieldAndParentsRecursively(parentField))
                    .collect(Collectors.toList())
                    .toArray();

            field.setFieldValue(generator.invokeGenerator(parameterValues));

            return field.getFieldValue();
        }
    }
} public class DairyInstantiationException extends RuntimeException {

    public DairyInstantiationException(String message){
        super(message);
    }

    public DairyInstantiationException(String message, Exception e){
        super(message, e);
    }
} public class DuplicateAttributeException extends RuntimeException {
    public DuplicateAttributeException(String message){
        super(message);
    }
} public class FieldNotModifiableException extends RuntimeException {
    public FieldNotModifiableException(String message){
        super(message);
    }

    public FieldNotModifiableException(String message, Exception e){
        super(message, e);
    }
} public class GeneratorCouldNotBeInstantiatedException extends RuntimeException {

    public GeneratorCouldNotBeInstantiatedException(String message, Exception e){
        super(message, e);
    }
} public class GeneratorWithoutDefaultConstructor {

    public GeneratorWithoutDefaultConstructor(String something){
    }

    @Generator("aSingleValue")
    public String aString(){
        return "aString";
    }

} public class Generators {

    @Generator("aSingleValue")
    public String aSingleValue(){
        return "SimpleGeneratedValue";
    }

    @Generator("aString")
    public String aString(){
        return "aString";
    }

    @Generator("anInteger")
    public int anInteger(){
        return 666;
    }

    @Generator("aPrimitiveType")
    public int aPrimitiveType(){
        return 123;
    }

    @Generator("aPrimitiveType2")
    public int aPrimitiveType2(){
        return 123;
    }

    @Generator("aBoolean")
    public boolean aBoolean(){
        return false;
    }

    @Generator("parentAttribute")
    public String parent(){
        return "Parent : ";
    }

    @Generator("childAttribute")
    public String child(
            @References("parentAttribute") String parent
    ){
        return parent + "child";
    }

    @Generator("secondParentAttribute")
    public String parent2(){
        return "parent 2";
    }

    @Generator("childWithTwoParents")
    public String childWithTwoParents(
            @References("parentAttribute") String parent,
            @References("secondParentAttribute") String parent2
    ){
        return parent2 + " " + parent + "child";
    }

    @Generator("childWithTransitiveDependency")
    public String childWithTransitiveDependency(
            @References("childAttribute") String parent
    ){
        return parent + " with transitive dependency";
    }

    @Generator("complexType")
    public MultipleAttributes complexObject(){
        return DataCow.generateDairyFor(MultipleAttributes.class).milkCow();
    }

    @Generator("cirkular1")
    public String cirkular1(
            @References("cirkular2") String parent
    ){
        return "Cirkular 1, " + parent;
    }

    @Generator("cirkular2")
    public String cirkular2(
            @References("cirkular3") String parent
    ){
        return "Cirkular 2, " + parent;
    }

    @Generator("cirkular3")
    public String cirkular3(
            @References("cirkular1") String parent
    ){
        return "Cirkular 3, " + parent;
    }

    @Generator("nullValue")
    public String nullValue(){
        return null;
    }

    @Generator("primitiveNull")
    public Integer primitiveNull(){
        return null;
    }

    @Generator("referencesNotFound")
    public String referencesNotFound(
            @References("notFound") String notFound
    ){
        return "This one referenced notFound: " + notFound;
    }

} public class Generators2 {

    @Generator("aSingleValue2")
    public String aSingleValue(){
        return "SimpleGeneratedValue";
    }

} @WithGenerators(Generators.class)
public class ConstructorWithArgument {

    public ConstructorWithArgument(String someArg){}

    @Attribute("aSingleValue")
    public String aSingleValue;

} @WithGenerators(GeneratorWithoutDefaultConstructor.class)
public class SingleAttributeGeneratorWithNoDefaultConstructor {

    @Attribute("aSingleValue")
    public String aSingleValue;

}
import com.github.johan.backstrom.corev2.Configuration;
import com.github.johan.backstrom.corev2.DataCow;
import com.github.johan.backstrom.corev2.exception.DairyInstantiationException;
import com.github.johan.backstrom.corev2.exception.DuplicateAttributeException;
import com.github.johan.backstrom.corev2.exception.FieldNotModifiableException;
import com.github.johan.backstrom.corev2.exception.GeneratorCouldNotBeInstantiatedException;
import datacow2.generators.GeneratorWithoutDefaultConstructor;
import datacow2.generators.Generators;
import datacow2.generators.Generators2;
import datacow2.models.annotation.*;
import datacow2.models.simple.ConstructorWithArgument;
import datacow2.models.simple.SingleAttributeGeneratorWithNoDefaultConstructor;
import org.junit.Test;
import static org.junit.Assert.assertEquals;
package datacow2;



public class AnnotationTest {

    @Test
    public void multipleGenerators(){

        MultipleGenerators o = DataCow.generateDairyFor(MultipleGenerators.class)
                .milkCow();

        assertEquals("SimpleGeneratedValue", o.singleValue);
        assertEquals("SimpleGeneratedValue", o.singleValue2);
    }

    @Test(expected = DuplicateAttributeException.class)
    public void duplicateGeneratorIds(){
        SimpleObjectWithDupliacteGenerators o = DataCow.generateDairyFor(SimpleObjectWithDupliacteGenerators.class)
                .milkCow();
    }

    @Test
    public void specifyingGeneratorDirectlyInDataCow(){
        NoGeneratorSpecified o = DataCow.generateDairyFor(NoGeneratorSpecified.class)
                .withGenerator(Generators.class)
                .milkCow();

        assertEquals("aString", o.aString);
    }

    @Test
    public void specifyingMultipleGeneratorsDirectlyInDataCow(){
        NoGeneratorSpecifiedWithMultipleGenerators o = DataCow.generateDairyFor(NoGeneratorSpecifiedWithMultipleGenerators.class)
                .withGenerator(Generators.class)
                .withGenerator(Generators2.class)
                .milkCow();

        assertEquals("aString", o.aString);
        assertEquals("SimpleGeneratedValue", o.anotherValue);
    }

    @Test
    public void passGeneratorInstanceToDataCow(){
        NoGeneratorSpecified o = DataCow.generateDairyFor(NoGeneratorSpecified.class)
                .withGenerator(new Generators())
                .milkCow();

        assertEquals("aString", o.aString);
    }

    @Test(expected = FieldNotModifiableException.class)
    public void wrongReturnTypeOfGeneratorGivesError(){
        GeneratorWithWrongType o = DataCow.generateDairyFor(GeneratorWithWrongType.class)
                .milkCow();
        }

    @Test(expected = DairyInstantiationException.class)
    public void instantiateObjectWithSpecialConstructorAndExpectException(){
        ConstructorWithArgument o = DataCow.generateDairyFor(ConstructorWithArgument.class)