All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.sundr.builder.internal.functions.ClazzAs Maven / Gradle / Ivy

/*
 * Copyright 2015 The original authors.
 *
 *    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 io.sundr.builder.internal.functions;

import io.sundr.Function;
import io.sundr.FunctionFactory;
import io.sundr.Provider;
import io.sundr.builder.Constants;
import io.sundr.builder.TypedVisitor;
import io.sundr.builder.internal.BuilderContextManager;
import io.sundr.builder.internal.utils.BuilderUtils;
import io.sundr.codegen.CodegenContext;
import io.sundr.codegen.functions.ClassTo;
import io.sundr.codegen.model.Attributeable;
import io.sundr.codegen.model.Block;
import io.sundr.codegen.model.ClassRef;
import io.sundr.codegen.model.Method;
import io.sundr.codegen.model.MethodBuilder;
import io.sundr.codegen.model.Property;
import io.sundr.codegen.model.PropertyBuilder;
import io.sundr.codegen.model.Statement;
import io.sundr.codegen.model.StringStatement;
import io.sundr.codegen.model.TypeDef;
import io.sundr.codegen.model.TypeDefBuilder;
import io.sundr.codegen.model.TypeParamDef;
import io.sundr.codegen.model.TypeParamRef;
import io.sundr.codegen.model.TypeRef;
import io.sundr.codegen.utils.StringUtils;
import io.sundr.codegen.utils.TypeUtils;

import javax.lang.model.element.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.List;
import java.util.Set;

import static io.sundr.builder.Constants.*;
import static io.sundr.builder.internal.utils.BuilderUtils.*;
import static io.sundr.codegen.model.Attributeable.ALSO_IMPORT;

public class ClazzAs {

    public static final Function FLUENT_INTERFACE = FunctionFactory.wrap(new Function() {
        public TypeDef apply(TypeDef item) {
            List methods = new ArrayList();
            List nestedClazzes = new ArrayList();
            TypeDef fluentType = TypeAs.FLUENT_INTERFACE.apply(item);
            TypeDef fluentImplType = TypeAs.FLUENT_IMPL.apply(item);

            //The generic letter is always the last
            final TypeParamDef genericType = fluentType.getParameters().get(fluentType.getParameters().size() - 1);

            for (Property property : item.getProperties()) {
                final TypeRef unwrapped = TypeAs.combine(TypeAs.UNWRAP_ARRAY_OF, TypeAs.UNWRAP_COLLECTION_OF).apply(property.getTypeRef());
                if (property.isStatic()) {
                    continue;
                }
                if (!hasBuildableConstructorWithArgument(item, property) && !hasOrInheritsSetter(item, property)) {
                    continue;
                }

                Property toAdd = new PropertyBuilder(property)
                        .withModifiers(0)
                        .addToAttributes(OUTER_INTERFACE, fluentType)
                        .addToAttributes(OUTER_CLASS, fluentImplType)
                        .addToAttributes(GENERIC_TYPE_REF, genericType.toReference())
                        .build();

                boolean isBuildable = isBuildable(unwrapped);
                boolean isArray = isArray(toAdd.getTypeRef());
                boolean isSet = isSet(toAdd.getTypeRef());
                boolean isList = isList(toAdd.getTypeRef());
                boolean isMap = isMap(toAdd.getTypeRef());
                boolean isCollection = isSet || isList;
                boolean isAbstract = isAbstract(unwrapped);

                Set descendants = Decendants.PROPERTY_BUILDABLE_DESCENDANTS.apply(toAdd);
                toAdd = new PropertyBuilder(toAdd).addToAttributes(DESCENDANTS, descendants).build();

                if (isArray) {
                    Property asList = arrayAsList(toAdd);
                    methods.add(ToMethod.WITH_ARRAY.apply(toAdd));
                    methods.add(ToMethod.GETTER_ARRAY.apply(toAdd));
                    methods.add(ToMethod.ADD_TO_COLLECTION.apply(asList));
                    methods.add(ToMethod.REMOVE_FROM_COLLECTION.apply(asList));
                    toAdd = asList;
                } else if (isSet || isList) {
                    methods.add(ToMethod.ADD_TO_COLLECTION.apply(toAdd));
                    methods.add(ToMethod.REMOVE_FROM_COLLECTION.apply(toAdd));
                    methods.add(ToMethod.GETTER.apply(toAdd));
                    methods.add(ToMethod.WITH.apply(toAdd));
                    methods.add(ToMethod.WITH_ARRAY.apply(toAdd));
                } else if (isMap) {
                    methods.add(ToMethod.ADD_TO_MAP.apply(toAdd));
                    methods.add(ToMethod.ADD_MAP_TO_MAP.apply(toAdd));
                    methods.add(ToMethod.REMOVE_FROM_MAP.apply(toAdd));
                    methods.add(ToMethod.REMOVE_MAP_FROM_MAP.apply(toAdd));
                    methods.add(ToMethod.GETTER.apply(toAdd));
                    methods.add(ToMethod.WITH.apply(toAdd));
                } else {
                    toAdd = new PropertyBuilder(toAdd).addToAttributes(BUILDABLE, isBuildable).build();
                    methods.add(ToMethod.GETTER.apply(toAdd));
                    methods.add(ToMethod.WITH.apply(toAdd));
                }

                if (isMap) {
                    //
                } else if (isBuildable && !isAbstract) {
                    methods.add(ToMethod.WITH_NEW_NESTED.apply(toAdd));
                    methods.add(ToMethod.WITH_NEW_LIKE_NESTED.apply(toAdd));
                    if (!isCollection && !isArray) {
                        methods.add(ToMethod.EDIT_NESTED.apply(toAdd));
                        methods.add(ToMethod.EDIT_OR_NEW.apply(toAdd));
                        methods.add(ToMethod.EDIT_OR_NEW_LIKE.apply(toAdd));
                    }
                    methods.addAll(ToMethod.WITH_NESTED_INLINE.apply(toAdd));
                    nestedClazzes.add(PropertyAs.NESTED_INTERFACE.apply(toAdd));
                } else if (!descendants.isEmpty()) {
                    for (Property descendant : descendants) {
                        if (isCollection(descendant.getTypeRef())) {
                            methods.add(ToMethod.ADD_TO_COLLECTION.apply(descendant));
                            methods.add(ToMethod.REMOVE_FROM_COLLECTION.apply(descendant));
                        } else {
                            methods.add(ToMethod.WITH.apply(descendant));
                        }

                        methods.add(ToMethod.WITH_NEW_NESTED.apply(descendant));
                        methods.add(ToMethod.WITH_NEW_LIKE_NESTED.apply(descendant));
                        methods.addAll(ToMethod.WITH_NESTED_INLINE.apply(descendant));
                        nestedClazzes.add(PropertyAs.NESTED_INTERFACE.apply(descendant));
                    }
                }
            }

            return new TypeDefBuilder(fluentType)
                    .withInnerTypes(nestedClazzes)
                    .withMethods(methods)
                    .build();

        }
    });

    public static final Function FLUENT_IMPL = FunctionFactory.wrap(new Function() {
        public TypeDef apply(TypeDef item) {
            List constructors = new ArrayList();
            List methods = new ArrayList();
            List nestedClazzes = new ArrayList();
            final List properties = new ArrayList();
            TypeDef fluentType = TypeAs.FLUENT_INTERFACE.apply(item);
            final TypeDef fluentImplType = TypeAs.FLUENT_IMPL.apply(item);

            //The generic letter is always the last
            final TypeParamDef genericType = fluentImplType.getParameters().get(fluentImplType.getParameters().size() - 1);

            Method emptyConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .build();

            Method instanceConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(item.toReference())
                    .withName("instance").and()
                    .withNewBlock()
                    .withStatements(toInstanceConstructorBody(item, ""))
                    .endBlock()
                    .build();

            constructors.add(emptyConstructor);
            constructors.add(instanceConstructor);

            for (final Property property : item.getProperties()) {
                final TypeRef unwrapped = TypeAs.combine(TypeAs.UNWRAP_ARRAY_OF, TypeAs.UNWRAP_COLLECTION_OF).apply(property.getTypeRef());

                if (property.isStatic()) {
                    continue;
                }
                if (!hasBuildableConstructorWithArgument(item, property) && !hasOrInheritsSetter(item, property)) {
                    continue;
                }


                final boolean isBuildable = isBuildable(unwrapped);
                final boolean isArray = isArray(property.getTypeRef());
                final boolean isSet = isSet(property.getTypeRef());
                final boolean isList = isList(property.getTypeRef());
                final boolean isMap = isMap(property.getTypeRef());
                final boolean isCollection = isSet || isList;
                final boolean isAbstract = isAbstract(unwrapped);

                Property toAdd = new PropertyBuilder(property)
                        .withModifiers(TypeUtils.modifiersToInt(Modifier.PRIVATE))
                        .addToAttributes(OUTER_INTERFACE, fluentType)
                        .addToAttributes(OUTER_CLASS, fluentImplType)
                        .addToAttributes(GENERIC_TYPE_REF, genericType.toReference())
                        .accept(new TypedVisitor() {
                            public void visit(PropertyBuilder builder) {
                                if (isArray || isList) {
                                    ClassRef listRef =  ARRAY_LIST.toReference(unwrapped);
                                    builder.addToAttributes(INIT, "new " + listRef+ "()")
                                            .addToAttributes(ALSO_IMPORT, listRef);
                                } else if (isSet) {
                                    ClassRef setRef = LINKED_HASH_SET.toReference(unwrapped);
                                    builder.addToAttributes(INIT, "new " + setRef + "()")
                                            .addToAttributes(ALSO_IMPORT, setRef);
                                } else if (isMap) {
                                    List arguments = ((ClassRef)property.getTypeRef()).getArguments();
                                    ClassRef mapRef = LINKED_HASH_MAP.toReference(arguments.toArray(new TypeRef[arguments.size()]));
                                    builder.addToAttributes(INIT, "new " + mapRef  + "()")
                                            .addToAttributes(ALSO_IMPORT, mapRef);
                                }
                            }
                        }).build();

                Set descendants = Decendants.PROPERTY_BUILDABLE_DESCENDANTS.apply(toAdd);
                toAdd = new PropertyBuilder(toAdd).addToAttributes(DESCENDANTS, descendants).build();

                if (isArray) {
                    Property asList = arrayAsList(toAdd);
                    methods.add(ToMethod.WITH_ARRAY.apply(toAdd));
                    methods.add(ToMethod.GETTER_ARRAY.apply(toAdd));
                    methods.add(ToMethod.ADD_TO_COLLECTION.apply(asList));
                    methods.add(ToMethod.REMOVE_FROM_COLLECTION.apply(asList));
                    toAdd = asList;
                } else if (isSet || isList) {
                    methods.add(ToMethod.ADD_TO_COLLECTION.apply(toAdd));
                    methods.add(ToMethod.REMOVE_FROM_COLLECTION.apply(toAdd));
                    methods.add(ToMethod.GETTER.apply(toAdd));
                    methods.add(ToMethod.WITH.apply(toAdd));
                    methods.add(ToMethod.WITH_ARRAY.apply(toAdd));
                } else if (isMap) {
                    methods.add(ToMethod.ADD_TO_MAP.apply(toAdd));
                    methods.add(ToMethod.ADD_MAP_TO_MAP.apply(toAdd));
                    methods.add(ToMethod.REMOVE_FROM_MAP.apply(toAdd));
                    methods.add(ToMethod.REMOVE_MAP_FROM_MAP.apply(toAdd));
                    methods.add(ToMethod.GETTER.apply(toAdd));
                    methods.add(ToMethod.WITH.apply(toAdd));
                } else {
                    methods.add(ToMethod.GETTER.apply(toAdd));
                    methods.add(ToMethod.WITH.apply(toAdd));
                }

                if (isMap) {
                    properties.add(toAdd);
                } else if (isBuildable && !isAbstract) {
                    methods.add(ToMethod.WITH_NEW_NESTED.apply(toAdd));
                    methods.add(ToMethod.WITH_NEW_LIKE_NESTED.apply(toAdd));
                    if (!isCollection && !isArray) {
                        methods.add(ToMethod.EDIT_NESTED.apply(toAdd));
                        methods.add(ToMethod.EDIT_OR_NEW.apply(toAdd));
                        methods.add(ToMethod.EDIT_OR_NEW_LIKE.apply(toAdd));
                    }
                    methods.addAll(ToMethod.WITH_NESTED_INLINE.apply(toAdd));
                    nestedClazzes.add(PropertyAs.NESTED_CLASS.apply(toAdd));
                    properties.add(buildableField(toAdd));
                } else if (descendants.isEmpty()) {
                    properties.add(toAdd);
                } else if (!descendants.isEmpty()) {
                    properties.add(buildableField(toAdd));
                    for (Property descendant : descendants) {
                        if (isCollection(descendant.getTypeRef())) {
                            methods.add(ToMethod.ADD_TO_COLLECTION.apply(descendant));
                            methods.add(ToMethod.REMOVE_FROM_COLLECTION.apply(descendant));
                        }  else {
                            methods.add(ToMethod.WITH.apply(descendant));
                        }
                        methods.add(ToMethod.WITH_NEW_NESTED.apply(descendant));
                        methods.add(ToMethod.WITH_NEW_LIKE_NESTED.apply(descendant));
                        methods.addAll(ToMethod.WITH_NESTED_INLINE.apply(descendant));
                        nestedClazzes.add(PropertyAs.NESTED_CLASS.apply(descendant));
                    }
                } else {
                    properties.add(buildableField(toAdd));
                }
            }

            Method equals = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .withReturnType(ClassTo.TYPEREF.apply(boolean.class))
                    .addNewArgument().withName("o").withTypeRef(Constants.OBJECT.toReference()).endArgument()
                    .withName("equals")
                    .withBlock(new Block(new Provider>() {
                        @Override
                        public List get() {
                            return toEquals(fluentImplType, properties);
                        }
                    })).build();

            methods.add(equals);

            return new TypeDefBuilder(fluentImplType)
                    .withConstructors(constructors)
                    .withProperties(properties)
                    .withInnerTypes(nestedClazzes)
                    .withMethods(methods)
                    .build();
        }
    });


    public static final Function BUILDER = FunctionFactory.wrap(new Function() {
        public TypeDef apply(final TypeDef item) {
            final Modifier[] modifiers = item.isAbstract()
                    ? new Modifier[]{Modifier.PUBLIC, Modifier.ABSTRACT}
                    : new Modifier[]{Modifier.PUBLIC};

            final TypeDef builderType = TypeAs.BUILDER.apply(item);
            ClassRef instanceRef = item.toInternalReference();

            ClassRef fluent = TypeAs.FLUENT_REF.apply(item);

            List constructors = new ArrayList();
            List methods = new ArrayList();
            final List fields = new ArrayList();

            Property fluentProperty = new PropertyBuilder().withTypeRef(fluent).withName("fluent").build();
            Property validationEnabledProperty = new PropertyBuilder().withTypeRef(BOOLEAN_REF).withName("validationEnabled").build();

            fields.add(fluentProperty);
            fields.add(validationEnabledProperty);

            Method emptyConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .withNewBlock()
                    .addNewStringStatementStatement("this(true);")
                    .endBlock()
                    .build();

            Method validationConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                        .withTypeRef(BOOLEAN_REF)
                        .withName("validationEnabled")
                    .and()
                    .withNewBlock()
                    .addToStatements(new StringStatement(new Provider() {
                        @Override
                        public String get() {
                            return hasDefaultConstructor(item) ? "this(new " + item.getName() + "(), validationEnabled);" : "this.fluent = this; this.validationEnabled=validationEnabled;";
                        }
                    }))
                    .endBlock()
                    .build();


            Method fluentConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(fluent)
                    .withName("fluent")
                    .and()
                    .withNewBlock()
                    .addNewStringStatementStatement("this(fluent, true);")
                    .endBlock()
                    .build();

            Method fluentAndValidationConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(fluent)
                    .withName("fluent")
                    .and()
                    .addNewArgument()
                    .withTypeRef(BOOLEAN_REF)
                    .withName("validationEnabled")
                    .and()
                    .withNewBlock()
                    .addToStatements(new StringStatement(new Provider() {
                        @Override
                        public String get() {
                            return hasDefaultConstructor(item) ? "this(fluent, new " + item.getName() + "(), validationEnabled);" : "this.fluent = fluent; this.validationEnabled=validationEnabled;";
                        }
                    }))
                    .endBlock()
                    .build();

            Method instanceAndFluentCosntructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(fluent)
                    .withName("fluent")
                    .and()
                    .addNewArgument()
                    .withTypeRef(instanceRef)
                    .withName("instance").and()
                    .withNewBlock()
                    .addNewStringStatementStatement("this(fluent, instance, true);")
                    .endBlock()
                    .build();



            Method instanceAndFluentAndValidationCosntructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(fluent)
                    .withName("fluent")
                    .and()
                    .addNewArgument()
                    .withTypeRef(instanceRef)
                    .withName("instance").and()
                    .addNewArgument()
                    .withTypeRef(BOOLEAN_REF)
                    .withName("validationEnabled")
                    .and()
                    .withBlock(new Block(new Provider>() {
                        @Override
                        public List get() {
                            List instanceAndFluentConstructorStatements = toInstanceConstructorBody(item, "fluent");
                            instanceAndFluentConstructorStatements.add(new StringStatement("this.validationEnabled = validationEnabled; "));
                            return instanceAndFluentConstructorStatements;
                        }
                    })).build();

            Method instanceConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(instanceRef)
                    .withName("instance").and()
                    .withNewBlock()
                        .addNewStringStatementStatement("this(instance,true);")
                    .endBlock()
                    .build();


            Method instanceAndValidationConstructor = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .addNewArgument()
                    .withTypeRef(instanceRef)
                    .withName("instance").and()
                    .addNewArgument()
                    .withTypeRef(BOOLEAN_REF)
                    .withName("validationEnabled")
                    .and()
                    .withBlock(new Block(new Provider>() {
                        @Override
                        public List get() {
                            List statements = toInstanceConstructorBody(item, "this");
                            statements.add(new StringStatement("this.validationEnabled = validationEnabled; "));
                            return statements;
                        }
                    })).build();

            constructors.add(emptyConstructor);
            constructors.add(validationConstructor);
            constructors.add(fluentConstructor);
            constructors.add(fluentAndValidationConstructor);
            constructors.add(instanceAndFluentCosntructor);
            constructors.add(instanceAndFluentAndValidationCosntructor);
            constructors.add(instanceConstructor);
            constructors.add(instanceAndValidationConstructor);

            Method build = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(modifiers))
                    .withReturnType(instanceRef)
                    .withName("build")
                    .withBlock(new Block(new Provider>() {
                        @Override
                        public List get() {
                            return toBuild(item, item);
                        }
                    })).build();
            methods.add(build);

            //  private  void validate(T item) {}
            final Boolean validationGloballyEnabled = item.getAttributes().containsKey(VALIDATION_ENABLED) && (Boolean) item.getAttributes().get(VALIDATION_ENABLED);
            Method validate = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PRIVATE))
                    .withParameters(T)
                    .withReturnType(Constants.VOID)
                    .addNewArgument()
                        .withName("item")
                        .withTypeRef(T_REF)
                    .endArgument()
                    .withName("validate")
                    .withBlock(new Block(new Provider>() {
                        @Override
                        public List get() {
                            return toValidate(item,validationGloballyEnabled);
                        }
                    }))
                    .addToAttributes(ALSO_IMPORT, validationGloballyEnabled ? Constants.VALIDATION_REFS : Collections.emptyList())
                    .build();

            methods.add(validate);

            Method equals = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(Modifier.PUBLIC))
                    .withReturnType(ClassTo.TYPEREF.apply(boolean.class))
                    .addNewArgument().withName("o").withTypeRef(Constants.OBJECT.toReference()).endArgument()
                    .withName("equals")
                    .withBlock(new Block(new Provider>() {
                        @Override
                        public List get() {
                            return toEquals(builderType, fields);
                        }
                    })).build();

            methods.add(equals);

            return new TypeDefBuilder(builderType)
                    .withModifiers(TypeUtils.modifiersToInt(modifiers))
                    .withProperties(fields)
                    .withConstructors(constructors)
                    .withMethods(methods)
                    .build();
        }

    });

    public static final Function EDITABLE_BUILDER = FunctionFactory.wrap(new Function() {
        public TypeDef apply(final TypeDef item) {
            final Modifier[] modifiers = item.isAbstract()
                    ? new Modifier[]{Modifier.PUBLIC, Modifier.ABSTRACT}
                    : new Modifier[]{Modifier.PUBLIC};

            final TypeDef editable = EDITABLE.apply(item);
            return new TypeDefBuilder(BUILDER.apply(item)).accept(new TypedVisitor() {
                public void visit(MethodBuilder builder) {
                    if (builder.getName() != null && builder.getName().equals("build")) {
                        builder.withModifiers(TypeUtils.modifiersToInt(modifiers));
                        builder.withReturnType(editable.toInternalReference());
                        builder.withBlock(new Block(new Provider>() {
                            @Override
                            public List get() {
                                return toBuild(editable, editable);
                            }
                        }));
                    }
                }
            }).build();
        }
    });

    public static final Function EDITABLE = FunctionFactory.wrap(new Function() {
        public TypeDef apply(TypeDef item) {
            Modifier[] modifiers = item.isAbstract()
                    ? new Modifier[]{Modifier.PUBLIC, Modifier.ABSTRACT}
                    : new Modifier[]{Modifier.PUBLIC};

            TypeDef editableType = TypeAs.EDITABLE.apply(item);
            final TypeDef builderType = TypeAs.BUILDER.apply(item);

            List constructors = new ArrayList();
            List methods = new ArrayList();

            for (Method constructor : item.getConstructors()) {
                constructors.add(superConstructorOf(constructor, editableType));
            }

            Method edit = new MethodBuilder()
                    .withModifiers(TypeUtils.modifiersToInt(modifiers))
                    .withReturnType(builderType.toInternalReference())
                    .withName("edit")
                    .withNewBlock()
                        .addToStatements(new StringStatement(new Provider() {
                            @Override
                            public String get() {
                                return "return new " + builderType.getName() + "(this);";
                            }
                        }))
                    .endBlock()
                    .build();

            methods.add(edit);

            //We need to treat the editable classes as buildables themselves.
            return CodegenContext.getContext().getDefinitionRepository().register(
                    BuilderContextManager.getContext().getBuildableRepository().register(new TypeDefBuilder(editableType)
                            .withModifiers(TypeUtils.modifiersToInt(modifiers))
                            .withConstructors(constructors)
                            .withMethods(methods)
                            .addToAttributes(BUILDABLE, true)
                            .addToAttributes(GENERATED, true) // We want to know that its a generated type...
                            .build())
            );
        }
    });

    private static Property arrayAsList(Property property) {
        return new PropertyBuilder(property)
                .withTypeRef(TypeAs.ARRAY_AS_LIST.apply(TypeAs.BOXED_OF.apply(property.getTypeRef())))
                .build();
    }

    private static List toInstanceConstructorBody(TypeDef clazz, String fluent) {
        Method constructor = findBuildableConstructor(clazz);
        List statements = new ArrayList();
        String ref = fluent;

        //We may use a reference to fluent or we may use directly "this". So we need to check.
        if (fluent != null && !fluent.isEmpty()) {
            statements.add(new StringStatement("this.fluent = " + fluent + "; "));
        } else {
            ref = "this";
        }

        for (Property property : constructor.getArguments()) {
            Method getter = findGetter(clazz, property);
            if (getter != null) {
                String cast = property.getTypeRef() instanceof TypeParamRef ? "(" + property.getTypeRef().toString() + ")" : "";
                statements.add(new StringStatement(new StringBuilder().append(ref).append(".with").append(property.getNameCapitalized()).append("(").append(cast).append("instance.").append(getter.getName()).append("()); ").toString()));
            } else {
                throw new IllegalStateException("Could not find getter for property:" + property + " in class:" + clazz);
            }
        }

        TypeDef target = clazz;
        //Iterate parent objects and check for properties with setters but not ctor arguments.
        while (target != null && !OBJECT.equals(target) && BuilderUtils.isBuildable(target)) {
            for (Property property : target.getProperties()) {
                if (!hasBuildableConstructorWithArgument(target, property) && hasSetter(target, property)) {
                    String withName = "with" + property.getNameCapitalized();
                    String getterName = BuilderUtils.findGetter(target, property).getName();
                    statements.add(new StringStatement(new StringBuilder().append(ref).append(".").append(withName).append("(instance.").append(getterName).append("());\n").toString()));
                }
            }

            if (!target.getExtendsList().isEmpty()) {
                target = BuilderContextManager.getContext().getBuildableRepository().getBuildable(target.getExtendsList().iterator().next());
            } else {
                return statements;
            }
        }
        return statements;
    }

    private static List toBuild(final TypeDef clazz, final TypeDef instanceType) {
        Method constructor = findBuildableConstructor(clazz);
        List statements = new ArrayList();

        statements.add(new StringStatement(new StringBuilder()
                .append(instanceType.getName()).append(" buildable = new ").append(instanceType.getName()).append("(")
                .append(StringUtils.join(constructor.getArguments(), new Function() {
                    public String apply(Property item) {
                        String prefix = isBoolean(item.getTypeRef()) ? "is" : "get";
                        //String cast = genericTypes.contains(item.getTypeRef().getFullyQualifiedName()) ? "("+item.getType().getFullyQualifiedName()+")" : "";
                        return "fluent." + prefix + item.getNameCapitalized() + "()";
                    }
                }, ","))
                .append(");")
                .toString()));


        TypeDef target = clazz;

        //Iterate parent objects and check for properties with setters but not ctor arguments.
        while (target != null && !OBJECT.equals(target) && BuilderUtils.isBuildable(target)) {
            for (Property property : target.getProperties()) {
                if (!hasBuildableConstructorWithArgument(target, property) && hasSetter(target, property)) {
                    String setterName = "set" + property.getNameCapitalized();
                    String getterName = BuilderUtils.findGetter(target, property).getName();
                    statements.add(new StringStatement(new StringBuilder()
                            .append("buildable.").append(setterName).append("(fluent.").append(getterName).append("());")
                            .toString()));

                }
            }
            target = BuilderContextManager.getContext().getBuildableRepository().getBuildable(target.getExtendsList().iterator().next());
        }

        statements.add(new StringStatement("validate(buildable);"));
        statements.add(new StringStatement("return buildable;"));
        return statements;
    }


    private static List toEquals(TypeDef type, Collection properties) {
        List statements = new ArrayList();

        String simpleName = type.getName();
        ClassRef superClass = type.getExtendsList().isEmpty() ? TypeDef.OBJECT_REF : type.getExtendsList().iterator().next();
        statements.add(new StringStatement("if (this == o) return true;"));
        statements.add(new StringStatement("if (o == null || getClass() != o.getClass()) return false;"));

        //If base fluent is the superclass just skip.
        if (!Constants.BASE_FLUENT.getFullyQualifiedName().equals(superClass.getDefinition().getFullyQualifiedName())) {
            statements.add(new StringStatement("if (!super.equals(o)) return false;"));
        }
        statements.add(new StringStatement(new StringBuilder().append(simpleName).append(" that = (").append(simpleName).append(") o;").toString()));

        for (Property property : properties) {
            String name = property.getName();
            if (BuilderUtils.isPrimitive(property.getTypeRef())) {
                statements.add(new StringStatement(new StringBuilder().append("if (").append(name).append(" != ").append("that.").append(name).append(") return false;").toString()));
            } else if (property.getTypeRef() instanceof ClassRef && Decendants.isDescendant(type, ((ClassRef) property.getTypeRef()).getDefinition())) {
                statements.add(new StringStatement(new StringBuilder()
                        .append("if (").append(name).append(" != null &&").append(name).append(" != this ? !").append(name).append(".equals(that.").append(name).append(") :")
                        .append("that.").append(name).append(" != null &&").append(name).append(" != this ) return false;").append("\n")
                        .toString()));
            } else {
                statements.add(new StringStatement(new StringBuilder().append("if (").append(name).append(" != null ? !").append(name).append(".equals(that.").append(name).append(") :")
                        .append("that.").append(name).append(" != null) return false;").toString()));

            }
        }

        statements.add(new StringStatement("return true;"));
        return statements;
    }


    private static List toValidate(TypeDef type, boolean enabled) {
        List statements = new ArrayList();
        if (enabled) {
            statements.add(new StringStatement("if (!validationEnabled) { return; }"));
            statements.add(new StringStatement("Validator validator = null;"));
            statements.add(new StringStatement("try {"));
            statements.add(new StringStatement("    ValidatorFactory factory = Validation.buildDefaultValidatorFactory();"));
            statements.add(new StringStatement("    validator = factory.getValidator();"));
            statements.add(new StringStatement("} catch(ValidationException e) {return;}"));
            statements.add(new StringStatement("Set> violations = validator.validate(item);"));
            statements.add(new StringStatement("if (!violations.isEmpty()) {"));
            statements.add(new StringStatement("throw new ConstraintViolationException(violations);"));
            statements.add(new StringStatement(" }"));
        }
        return statements;
    }


    private static Method superConstructorOf(Method constructor, TypeDef constructorType) {
        return new MethodBuilder(constructor)
                .withReturnType(constructorType.toReference())
                .withNewBlock()
                .addNewStringStatementStatement("super(" + StringUtils.join(constructor.getArguments(), new Function() {
                    public String apply(Property item) {
                        return item.getName();
                    }
                }, ", ") + ");")
                .endBlock()
                .build();
    }

    private static Property buildableField(Property property) {
        TypeRef typeRef = property.getTypeRef();
        TypeRef unwrapped = TypeAs.combine(TypeAs.UNWRAP_COLLECTION_OF, TypeAs.UNWRAP_ARRAY_OF).apply(typeRef);
        ClassRef classRef = (ClassRef) typeRef;
        ClassRef builderType = TypeAs.VISITABLE_BUILDER.apply(unwrapped);


        if (isList(classRef)) {
            ClassRef listRef =  ARRAY_LIST.toReference(builderType);
            return new PropertyBuilder(property).withTypeRef(LIST.toReference(builderType))
                    .addToAttributes(INIT, " new " + listRef + "()")
                    .addToAttributes(ALSO_IMPORT, alsoImport(property, listRef, builderType))
                    .build();
        } else if (isSet(classRef)) {
            ClassRef setRef = LINKED_HASH_SET.toReference(builderType);
            return new PropertyBuilder(property).withTypeRef(SET.toReference(builderType))
                    .addToAttributes(INIT, " new " + setRef+ "()")
                    .addToAttributes(ALSO_IMPORT,  alsoImport(property, setRef, builderType))
                    .build();
        } else {
            return new PropertyBuilder(property).withTypeRef(builderType)
                    .addToAttributes(ALSO_IMPORT, alsoImport(property, builderType))
                    .build();
        }
    }

    private static List alsoImportAsList(Attributeable attributeable) {
        List result = new ArrayList();
        if (attributeable.getAttributes().containsKey(ALSO_IMPORT)) {
            Object existingImports = attributeable.getAttributes().get(ALSO_IMPORT);
            if (existingImports instanceof Collection) {
                result.addAll((Collection) existingImports);
            } else if (existingImports instanceof ClassRef) {
                result.add((ClassRef) existingImports);
            } else {
                throw new IllegalStateException("Illegal value for ALSO_IMPORT attribute. Expected a Collection, but found :["+existingImports+"].");
            }
        }
        return result;
    }

    private static List alsoImport(Attributeable attributeable, ClassRef... refs) {
        List result = alsoImportAsList(attributeable);
        for (ClassRef ref : refs) {
            result.add(ref);
        }
        return result;
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy