Bug 1411-5 #6: MDSAL Binding2 Java API Generator 94/46894/22
authorMartin Ciglan <mciglan@cisco.com>
Wed, 23 Nov 2016 09:57:54 +0000 (10:57 +0100)
committerRobert Varga <nite@hq.sk>
Tue, 10 Jan 2017 21:46:34 +0000 (21:46 +0000)
- builder generator, renderer and templates
- fixed minor issues
- bit of cleanup, review comments implemented

Change-Id: I7cedfb4fde4e441b7d68b2717fbb4f8ea6d3d77e
Signed-off-by: Filip Gregor <fgregor@cisco.com>
Signed-off-by: Martin Ciglan <mciglan@cisco.com>
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding2/java/api/generator/BuilderGenerator.java [new file with mode: 0644]
binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding2/java/api/generator/renderers/BuilderRenderer.java [new file with mode: 0644]
binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding2/builderConstructorHelperTemplate.scala.txt [new file with mode: 0644]
binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding2/builderTemplate.scala.txt [new file with mode: 0644]

diff --git a/binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding2/java/api/generator/BuilderGenerator.java b/binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding2/java/api/generator/BuilderGenerator.java
new file mode 100644 (file)
index 0000000..a2821f8
--- /dev/null
@@ -0,0 +1,60 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding2.java.api.generator;
+
+import com.google.common.annotations.Beta;
+import org.opendaylight.mdsal.binding2.java.api.generator.renderers.BuilderRenderer;
+import org.opendaylight.mdsal.binding2.model.api.CodeGenerator;
+import org.opendaylight.mdsal.binding2.model.api.GeneratedTransferObject;
+import org.opendaylight.mdsal.binding2.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding2.model.api.Type;
+import org.opendaylight.mdsal.binding2.model.api.UnitName;
+import org.opendaylight.mdsal.binding2.spec.Augmentable;
+import org.opendaylight.mdsal.binding2.spec.Augmentation;
+import org.opendaylight.yangtools.concepts.Identifier;
+
+/**
+ * Transformer of the data from the virtual form to JAVA programming language.
+ * The result source code represent java class. For generation of the source
+ * code is used the template written in Twirl (Scala based) language.
+ */
+@Beta
+public final class BuilderGenerator implements CodeGenerator {
+
+    @Override
+    public String generate(Type type) {
+        if ((type instanceof GeneratedType) && !(type instanceof GeneratedTransferObject)) {
+            final GeneratedType genType = (GeneratedType) type;
+            return new BuilderRenderer(genType).generateTemplate();
+        } else {
+            return "";
+        }
+    }
+
+    @Override
+    public boolean isAcceptable(Type type) {
+        if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
+            for (Type t : ((GeneratedType) type).getImplements()) {
+                // "rpc" and "grouping" elements do not implement Augmentable
+                if (t.getFullyQualifiedName().equals(Augmentable.class.getName())) {
+                    return true;
+                } else if (t.getFullyQualifiedName().equals(Augmentation.class.getName())) {
+                    return true;
+                }
+
+            }
+        }
+        return false;
+    }
+
+    @Override
+    public Identifier getUnitName(Type type) {
+        return new UnitName(type.getName());
+    }
+}
diff --git a/binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding2/java/api/generator/renderers/BuilderRenderer.java b/binding2/mdsal-binding2-java-api-generator/src/main/java/org/opendaylight/mdsal/binding2/java/api/generator/renderers/BuilderRenderer.java
new file mode 100644 (file)
index 0000000..96acc65
--- /dev/null
@@ -0,0 +1,300 @@
+/*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ */
+
+package org.opendaylight.mdsal.binding2.java.api.generator.renderers;
+
+import static org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.DOT;
+import static org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.getPropertyList;
+import static org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.toFirstLower;
+
+import com.google.common.base.Preconditions;
+import com.google.common.base.Strings;
+import com.google.common.collect.Collections2;
+import com.google.common.collect.ImmutableSortedSet;
+import java.lang.reflect.Method;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collection;
+import java.util.Collections;
+import java.util.Comparator;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.LinkedHashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Set;
+import org.opendaylight.mdsal.binding2.generator.util.ReferencedTypeImpl;
+import org.opendaylight.mdsal.binding2.generator.util.Types;
+import org.opendaylight.mdsal.binding2.generator.util.generated.type.builder.GeneratedTOBuilderImpl;
+import org.opendaylight.mdsal.binding2.java.api.generator.util.AlphabeticallyTypeMemberComparator;
+import org.opendaylight.mdsal.binding2.model.api.GeneratedProperty;
+import org.opendaylight.mdsal.binding2.model.api.GeneratedTransferObject;
+import org.opendaylight.mdsal.binding2.model.api.GeneratedType;
+import org.opendaylight.mdsal.binding2.model.api.MethodSignature;
+import org.opendaylight.mdsal.binding2.model.api.ParameterizedType;
+import org.opendaylight.mdsal.binding2.model.api.Type;
+import org.opendaylight.mdsal.binding2.spec.Augmentable;
+import org.opendaylight.mdsal.binding2.spec.TreeNode;
+import org.opendaylight.mdsal.binding2.txt.builderConstructorHelperTemplate;
+import org.opendaylight.mdsal.binding2.txt.builderTemplate;
+import org.opendaylight.yangtools.concepts.Builder;
+import org.opendaylight.yangtools.concepts.Identifiable;
+
+public class BuilderRenderer extends BaseRenderer {
+
+    /**
+     * Set of class attributes (fields) which are derived from the getter methods names
+     */
+    private final Set<GeneratedProperty> properties;
+
+    /**
+     * Set of name from properties
+     */
+    private final Map<GeneratedProperty, String> importedNamesForProperties = new HashMap<>();
+
+    /**
+     * list of all imported names for template
+     */
+    private final Map<String, String> importedNames = new HashMap<>();
+
+    /**
+     * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
+     */
+    private GeneratedProperty augmentField;
+
+    public BuilderRenderer(final GeneratedType type) {
+        super(type);
+        this.properties = propertiesFromMethods(createMethods());
+        putToImportMap(Builder.class.getSimpleName(), Builder.class.getPackage().getName());
+    }
+
+    /**
+     * Creates set of generated property instances from getter <code>methods</code>.
+     *
+     * @param methods set of method signature instances which should be transformed to list of properties
+     * @return set of generated property instances which represents the getter <code>methods</code>
+     */
+    private Set<GeneratedProperty> propertiesFromMethods(final Collection<MethodSignature> methods) {
+        if (methods == null || methods.isEmpty()) {
+            return Collections.emptySet();
+        }
+        final Set<GeneratedProperty> result = new LinkedHashSet<>();
+        for (MethodSignature method : methods) {
+            final GeneratedProperty createdField = propertyFromGetter(method);
+            if (createdField != null) {
+                result.add(createdField);
+                importedNamesForProperties.put(createdField, importedName(createdField.getReturnType()));
+            }
+        }
+        return result;
+    }
+
+    /**
+     * Creates generated property instance from the getter <code>method</code> name and return type.
+     *
+     * @param method method signature from which is the method name and return type obtained
+     * @return generated property instance for the getter <code>method</code>
+     * @throws IllegalArgumentException
+     *  <li>if the <code>method</code> equals <code>null</code></li>
+     *  <li>if the name of the <code>method</code> equals <code>null</code></li>
+     *  <li>if the name of the <code>method</code> is empty</li>
+     *  <li>if the return type of the <code>method</code> equals <code>null</code></li>
+     */
+    private GeneratedProperty propertyFromGetter(final MethodSignature method) {
+        Preconditions.checkArgument(method != null, "Method cannot be NULL");
+        Preconditions.checkArgument(!Strings.isNullOrEmpty(method.getName()), "Method name cannot be NULL or empty");
+        Preconditions.checkArgument(method.getReturnType() != null, "Method return type reference cannot be NULL");
+        final String prefix = Types.BOOLEAN.equals(method.getReturnType()) ? "is" : "get";
+        if (method.getName().startsWith(prefix)) {
+            final String fieldName = toFirstLower(method.getName().substring(prefix.length()));
+            final GeneratedTOBuilderImpl tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo");
+            tmpGenTO.addProperty(fieldName)
+                    .setReturnType(method.getReturnType());
+            return tmpGenTO.toInstance().getProperties().get(0);
+        }
+        return null;
+    }
+
+    /**
+     * Returns set of method signature instances which contains all the methods of the <code>genType</code>
+     * and all the methods of the implemented interfaces.
+     *
+     * @returns set of method signature instances
+     */
+    private Set<MethodSignature> createMethods() {
+        final Set<MethodSignature> methods = new LinkedHashSet<>();
+        methods.addAll(getType().getMethodDefinitions());
+        collectImplementedMethods(methods, getType().getImplements());
+        final Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(
+                new AlphabeticallyTypeMemberComparator<MethodSignature>())
+                .addAll(methods)
+                .build();
+        return sortedMethods;
+    }
+
+    /**
+     * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
+     * and recursively their implemented interfaces.
+     *
+     * @param methods set of method signatures
+     * @param implementedIfcs list of implemented interfaces
+     */
+    private void collectImplementedMethods(final Set<MethodSignature> methods, List<Type> implementedIfcs) {
+        if (implementedIfcs != null && !implementedIfcs.isEmpty()) {
+            for (Type implementedIfc : implementedIfcs) {
+                if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
+                    final GeneratedType ifc = (GeneratedType) implementedIfc;
+                    methods.addAll(ifc.getMethodDefinitions());
+                    collectImplementedMethods(methods, ifc.getImplements());
+                } else if (Augmentable.class.getName().equals(implementedIfc.getFullyQualifiedName())) {
+                    for (Method method : Augmentable.class.getMethods()) {
+                        if ("getAugmentation".equals(method.getName())) {
+                            final String fullyQualifiedName = method.getReturnType().getName();
+                            final String aPackage = getPackage(fullyQualifiedName);
+                            final String name = getName(fullyQualifiedName);
+                            final GeneratedTOBuilderImpl generatedTOBuilder = new GeneratedTOBuilderImpl(aPackage, name);
+                            final ReferencedTypeImpl referencedType = new ReferencedTypeImpl(aPackage, name);
+                            final ReferencedTypeImpl generic = new ReferencedTypeImpl(getType().getPackageName(),
+                                    getType().getName());
+                            final ParameterizedType parametrizedReturnType = Types.parameterizedTypeFor(referencedType, generic);
+                            generatedTOBuilder.addMethod(method.getName()).setReturnType(parametrizedReturnType);
+                            augmentField = propertyFromGetter(generatedTOBuilder.toInstance().getMethodDefinitions().get(0));
+                            importedNames.put("map", importedName(Map.class));
+                            importedNames.put("hashMap", importedName(HashMap.class));
+                            importedNames.put("class", importedName(Class.class));
+//                            To do This is for third party, is it needed ?
+//                            importedNames.put("augmentationHolder", importedName(AugmentationHolder.class));
+                            importedNames.put("collections", importedName(Collections.class));
+                            importedNames.put("augmentFieldReturnType", importedName(augmentField.getReturnType()));
+                        }
+                    }
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the name of the package from <code>fullyQualifiedName</code>.
+     *
+     * @param fullyQualifiedName string with fully qualified type name (package + type)
+     * @return string with the package name
+     */
+    private String getPackage(final String fullyQualifiedName) {
+        final int lastDotIndex = fullyQualifiedName.lastIndexOf(DOT);
+        return (lastDotIndex == -1) ? "" : fullyQualifiedName.substring(0, lastDotIndex);
+    }
+
+    /**
+     * Returns the name of tye type from <code>fullyQualifiedName</code>
+     *
+     * @param fullyQualifiedName string with fully qualified type name (package + type)
+     * @return string with the name of the type
+     */
+    private String getName(final String fullyQualifiedName) {
+        final int lastDotIndex = fullyQualifiedName.lastIndexOf(DOT);
+        return (lastDotIndex == -1) ? fullyQualifiedName : fullyQualifiedName.substring(lastDotIndex + 1);
+    }
+
+    public static Set<Type> getAllIfcs(final Type type) {
+        final Set<Type> baseIfcs = new HashSet<>();
+        if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
+            for (Type impl : ((GeneratedType)type).getImplements()) {
+                if (impl instanceof GeneratedType && !(((GeneratedType)impl).getMethodDefinitions().isEmpty())) {
+                    baseIfcs.add(impl);
+                }
+                baseIfcs.addAll(getAllIfcs(impl));
+            }
+        }
+        return baseIfcs;
+    }
+
+    /**
+     * Method is used to find out if given type implements any interface from uses.
+     */
+    public static boolean hasImplementsFromUses(GeneratedType type) {
+        for (Type impl : getAllIfcs(type)) {
+            if ((impl instanceof GeneratedType) && !(((GeneratedType)impl).getMethodDefinitions().isEmpty())) {
+                return true;
+            }
+        }
+        return false;
+    }
+
+    public static Set<String> toListOfNames(final Set<Type> types) {
+        final Set<String> names = new HashSet<>();
+        for (Type currentType : types) {
+            names.add(currentType.getFullyQualifiedName());
+        }
+        return names;
+    }
+
+    @Override
+    protected String body() {
+        importedNames.put("genType", importedName(getType()));
+        importedNames.put("arrays", importedName(Arrays.class));
+        importedNames.put("objects", importedName(Objects.class));
+        importedNames.put("object", importedName(Object.class));
+        importedNames.put("string", importedName(String.class));
+        importedNames.put("stringBuilder", importedName(StringBuilder.class));
+        importedNames.put("treeNode", importedName(TreeNode.class));
+        // list for generate copy constructor
+        final String copyConstructorHelper = generateListForCopyConstructor();
+        List<String> getterMethods = new ArrayList<>(Collections2.transform(properties, this::getterMethod));
+
+        return builderTemplate.render(getType(), properties, importedNames, importedNamesForProperties, augmentField,
+                copyConstructorHelper, getterMethods)
+                .body();
+    }
+
+    private String generateListForCopyConstructor() {
+        final List allProps = new ArrayList<>(properties);
+        final boolean isList = implementsIfc(getType(), Types.parameterizedTypeFor(Types.typeForClass(Identifiable.class),
+                getType()));
+        final Type keyType = getKey(getType());
+        if (isList && keyType != null) {
+            final List<GeneratedProperty> keyProps = ((GeneratedTransferObject) keyType).getProperties();
+            final Comparator<GeneratedProperty> function = (GeneratedProperty p1, GeneratedProperty p2) -> {
+                String name2 = p1.getName();
+                String name3 = p2.getName();
+                return name2.compareTo(name3);
+            };
+            Collections.sort(keyProps, function);
+            for (GeneratedProperty keyProp : keyProps) {
+                removeProperty(allProps, keyProp.getName());
+            }
+            removeProperty(allProps, "key");
+            importedNames.put("keyTypeConstructor", importedName(keyType));
+            return builderConstructorHelperTemplate.render(allProps, keyProps, importedNames, getPropertyList(allProps))
+                    .body();
+        }
+        return builderConstructorHelperTemplate.render(allProps, null, importedNames, null).body();
+    }
+
+    private Type getKey(final GeneratedType genType) {
+        for (MethodSignature methodSignature : genType.getMethodDefinitions()) {
+            if ("getKey".equals(methodSignature.getName())) {
+                return methodSignature.getReturnType();
+            }
+        }
+        return null;
+    }
+
+    private boolean implementsIfc(final GeneratedType type, final Type impl) {
+        return type.getImplements().contains(impl);
+    }
+
+    private void removeProperty(final Collection<GeneratedProperty> properties, final String name) {
+        for (final GeneratedProperty property : properties) {
+            if (name.equals(property.getName())) {
+                properties.remove(property);
+                break;
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding2/builderConstructorHelperTemplate.scala.txt b/binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding2/builderConstructorHelperTemplate.scala.txt
new file mode 100644 (file)
index 0000000..edbd4ea
--- /dev/null
@@ -0,0 +1,33 @@
+@*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *@
+
+@import org.opendaylight.mdsal.binding2.model.api.GeneratedProperty
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.fieldName
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.getterMethodName
+
+@(allProps: List[GeneratedProperty], keyProps: List[GeneratedProperty], importedNames: Map[String, String],
+keyPropsList: String)
+@if(keyProps != null) {
+if (base.getKey() == null) {
+    this._key = new @{importedNames.get("keyTypeConstructor")}(
+    @{keyPropsList}
+    );
+    @for(field <- keyProps) {
+        this.@{fieldName(field)} = base.
+        @{getterMethodName(field)}();
+    }
+} @{"else"} {
+    this._key = base.getKey();
+    @for(field <- keyProps) {
+        this.@{fieldName(field)} = _key.@{getterMethodName(field)}();
+    }
+}
+}
+@for(field <- allProps) {
+    this.@{fieldName(field)} = base.@{getterMethodName(field)}();
+}
diff --git a/binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding2/builderTemplate.scala.txt b/binding2/mdsal-binding2-java-api-generator/src/main/twirl/org/opendaylight/mdsal/binding2/builderTemplate.scala.txt
new file mode 100644 (file)
index 0000000..5c1bb9e
--- /dev/null
@@ -0,0 +1,424 @@
+@*
+ * Copyright (c) 2016 Cisco Systems, Inc. and others.  All rights reserved.
+ *
+ * This program and the accompanying materials are made available under the
+ * terms of the Eclipse Public License v1.0 which accompanies this distribution,
+ * and is available at http://www.eclipse.org/legal/epl-v10.html
+ *@
+
+@import java.util.List
+@import org.opendaylight.mdsal.binding2.model.api.ConcreteType
+@import org.opendaylight.mdsal.binding2.model.api.GeneratedType
+@import org.opendaylight.mdsal.binding2.model.api.GeneratedTransferObject
+@import org.opendaylight.mdsal.binding2.model.api.GeneratedProperty
+@import org.opendaylight.mdsal.binding2.model.api.Type
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.fieldName
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.formatDataForJavaDocBuilder
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.getSimpleNameForBuilder
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.getterMethodName
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.getRestrictions
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.propertyNameFromGetter
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.toFirstUpper
+@import org.opendaylight.mdsal.binding2.java.api.generator.util.TextTemplateUtil.wrapToDocumentation
+@import org.opendaylight.mdsal.binding2.java.api.generator.renderers.BuilderRenderer.getAllIfcs
+@import org.opendaylight.mdsal.binding2.java.api.generator.renderers.BuilderRenderer.hasImplementsFromUses
+@import org.opendaylight.mdsal.binding2.java.api.generator.renderers.BuilderRenderer.toListOfNames
+@import org.opendaylight.mdsal.binding2.java.api.generator.rangeGenerators.AbstractRangeGenerator
+@import org.opendaylight.mdsal.binding2.java.api.generator.rangeGenerators.LengthGenerator
+@import org.opendaylight.yangtools.concepts.Builder
+
+@(genType: GeneratedType, properties: Set[GeneratedProperty], importedNames: Map[String, String],
+ImportedNamesWithProperties: Map[GeneratedProperty, String], augmentField: GeneratedProperty, copyConstructorHelper: String,
+getterMethods: List[String])
+@if(genType != null) {
+@{wrapToDocumentation(formatDataForJavaDocBuilder(importedNames.get("genType")))}
+public class @{genType.getName}Builder implements @{getSimpleNameForBuilder} <@{importedNames.get("genType")}> {
+
+    @generateFields(false)
+
+    @generateAugmentField(false)
+
+    @generateConstructorsFromIfcs()
+
+    @generateCopyConstructor(false)
+
+    @generateMethodFieldsFrom()
+
+    @generateGetters(false)
+
+    @generateSetters()
+
+    public @{genType.getName} build() {
+        return new @{genType.getName}Impl(this);
+    }
+
+    private static final class @{genType.getName}Impl implements @{genType.getName} {
+
+        @implementedInterfaceGetter()
+
+        @generateFields(true)
+
+        @generateAugmentField(true)
+
+        @generateCopyConstructor(true)
+
+        @generateGetters(true)
+
+        @generateHashCode()
+
+        @generateEquals()
+
+        @generateToString()
+    }
+}
+}
+
+@**
+ * Template method which generates class attributes.
+ *
+ * @param isFinal value which specify whether field is|isn't final
+ * @param genType is genType
+ * @return string with class attributes and their types
+ *@
+@generateFields(isFinal: Boolean) = {
+    @if(ImportedNamesWithProperties != null) {
+        @for((key, value) <- ImportedNamesWithProperties) {
+            private @if(isFinal) { final}
+            @{value} @{fieldName(key)};
+        }
+    }
+}
+
+@**
+ * Template method which generates class attributes.
+ *
+ * @param boolean value which specify whether field is|isn't final
+ * @return string with class attributes and their types
+ *@
+@generateAugmentField(isPrivate: Boolean) = {
+    @if(augmentField != null) {
+        @if(isPrivate) {private }
+        @{importedNames.get("map")}<@{importedNames.get("class")}<? extends@{importedNames.get("augmentFieldReturnType")}>,
+        @{importedNames.get("augmentFieldReturnType")}>@{augmentField.getName} = @{importedNames.get("collections")}.emptyMap();
+    }
+}
+
+@implementedInterfaceGetter() = {
+    public @{importedNames.get("class")}<@{importedNames.get("genType")}> getImplementedInterface() {
+    return @{importedNames.get("genType")}.class;
+    }
+}
+
+@**
+ * Generate default constructor and constructor for every implemented interface from uses statements.
+ *@
+@generateConstructorsFromIfcs() = {
+    public @{genType.getName}Builder() {
+    }
+    @if(genType.isInstanceOf[GeneratedType] && !genType.isInstanceOf[GeneratedTransferObject]) {
+        @for(impl <- genType.asInstanceOf[GeneratedType].getImplements) {
+            @generateConstructorFromIfc(impl)
+        }
+    }
+}
+
+@generateMethodFieldsFrom() = {
+    @if(genType.isInstanceOf[GeneratedType] && !genType.isInstanceOf[GeneratedTransferObject]) {
+        @if(hasImplementsFromUses(genType.asInstanceOf[GeneratedType])) {
+            /**
+             *Set fields from given grouping argument. Valid argument is instance of one of following types:
+             * <ul>
+             @for(impl <- getAllIfcs(genType.asInstanceOf[GeneratedType])) {
+             * <li>@{impl.getFullyQualifiedName}</li>
+             }
+             * </ul>
+             *
+             * @@param arg grouping object
+             * @@throws IllegalArgumentException if given argument is none of valid types
+            */
+
+            public void fieldsFrom(@{importedNames.get("treeNode")} arg) {
+                boolean isValidArg = false;
+                @for(impl <- getAllIfcs(genType.asInstanceOf[GeneratedType])) {
+                    @if(impl.isInstanceOf[GeneratedType] && !impl.asInstanceOf[GeneratedType].getMethodDefinitions.isEmpty) {
+                        if (arg instanceof @{impl.asInstanceOf[GeneratedType].getFullyQualifiedName}) {
+                            @if(!impl.isInstanceOf[GeneratedTransferObject]) {
+                                @for(getter <- genType.asInstanceOf[GeneratedType].getMethodDefinitions) {
+                                    this._@{propertyNameFromGetter(getter)} = ((@{impl.asInstanceOf[GeneratedType].getFullyQualifiedName})arg).@{getter.getName}();
+                                }
+                            }
+                            isValidArg = true;
+                        }
+                    }
+                }
+                if (!isValidArg) {
+                    throw new IllegalArgumentException(
+                      "expected one of: @{toListOfNames(getAllIfcs(genType.asInstanceOf[GeneratedType]))} \n" +
+                      "but was: " + arg
+                    );
+                }
+            }
+        }
+    }
+}
+
+@**
+ * Generate constructor with argument of given type.
+ *@
+@generateConstructorFromIfc(impl: Type) = {
+    @if(impl.isInstanceOf[GeneratedType]) {
+        @if(!impl.asInstanceOf[GeneratedType].getMethodDefinitions.isEmpty) {
+            public @{genType.getName}Builder(
+            @{impl.getFullyQualifiedName} arg) {
+            @{printConstructorPropertySetter(impl)}
+            }
+        }
+        @for(implTypeImplement <- impl.asInstanceOf[GeneratedType].getImplements) {
+            @generateConstructorFromIfc(implTypeImplement)
+        }
+    }
+}
+
+@printConstructorPropertySetter(implementedIfc: Type) = {
+    @if(implementedIfc.isInstanceOf[GeneratedType] && !implementedIfc.isInstanceOf[GeneratedTransferObject]) {
+        @for(getter <- implementedIfc.asInstanceOf[GeneratedType].getMethodDefinitions) {
+            this._@{propertyNameFromGetter(getter)} = arg.@{getter.getName}();
+        }
+        @for(impl <- implementedIfc.asInstanceOf[GeneratedType].getImplements) {
+            @{printConstructorPropertySetter(impl)}
+        }
+    }
+}
+
+@generateCopyConstructor(impl: Boolean) = {
+    @if(impl) {private} else {public}
+    @{genType.getName}
+    @if(impl) {Impl} else {Builder}
+    (@{genType.getName}
+    @if(impl) {Builder} base) {
+    @{copyConstructorHelper}
+    @if(augmentField != null) {
+        @if(impl) {
+            switch (base.@{augmentField.getName}.size()) {
+            case 0:
+                this.@{augmentField.getName} = @{importedNames.get("collections")}.emptyMap();
+                break;
+            case 1:
+                final @{importedNames.get("map")}.Entry<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}> e = base.@{augmentField.getName}.entrySet().iterator().next();
+                this.@{augmentField.getName} = @{importedNames.get("collections")}.<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}>singletonMap(e.getKey(), e.getValue());
+                break;
+            default :
+                this.@{augmentField.getName} = new @{importedNames.get("hashMap")}<>(base.@{augmentField.getName});
+            }
+        } else {
+            if (base instanceof @{genType.getName}Impl) {
+                @{genType.getName}Impl impl = (@{genType.getName}Impl) base;
+                if (!impl.@{augmentField.getName}.isEmpty()) {
+                    this.@{augmentField.getName} = new @{importedNames.get("hashMap")}<>(impl.@{augmentField.getName});
+                }
+            } @{"else"} if (base instanceof @{importedNames.get("augmentationHolder")}) {
+                @@SuppressWarnings("unchecked")
+                @{importedNames.get("augmentationHolder")}<@{importedNames.get("genType")}> casted =(@{importedNames.get("augmentationHolder")}<@{importedNames.get("genType")}>) base;
+                if (!casted.augmentations().isEmpty()) {
+                    this.@{augmentField.getName} = new @{importedNames.get("hashMap")}<>(casted.augmentations());
+                }
+            }
+        }
+    }
+    }
+}
+
+
+@generateSetters() = {
+    @for(field <- properties) {
+        @if(!field.getReturnType.isInstanceOf[GeneratedType] && getRestrictions(field.getReturnType) != null) {
+            @if(getRestrictions(field.getReturnType).getRangeConstraints != null && !getRestrictions(field.getReturnType).getRangeConstraints.isEmpty) {
+                @{AbstractRangeGenerator.forType(field.getReturnType).generateRangeChecker(toFirstUpper(field.getName),
+                getRestrictions(field.getReturnType).getRangeConstraints)}
+            }
+            @if(getRestrictions(field.getReturnType).getLengthConstraints != null && !getRestrictions(field.getReturnType).getLengthConstraints.isEmpty) {
+                @{LengthGenerator.generateLengthChecker(fieldName(field), field.getReturnType,
+                getRestrictions(field.getReturnType).getLengthConstraints)}
+            }
+        }
+        public @{genType.getName}Builder set@{toFirstUpper(field.getName)}(final @{importedNames.get("augmentFieldReturnType")} value) {
+        @if(!field.getReturnType.isInstanceOf[GeneratedType] && getRestrictions(field.getReturnType) != null) {
+            if (value != null) {
+            @if(getRestrictions(field.getReturnType).getRangeConstraints != null && !getRestrictions(field.getReturnType).getRangeConstraints.isEmpty) {
+                @if(field.getReturnType.isInstanceOf[ConcreteType]) {
+                    @{AbstractRangeGenerator.forType(field.getReturnType).generateRangeCheckerCall(toFirstUpper(field.getName), "value")}
+                } else {
+                    @{AbstractRangeGenerator.forType(field.getReturnType).generateRangeCheckerCall(toFirstUpper(field.getName), "value.getValue()")}
+                }
+            }
+            @if(getRestrictions(field.getReturnType).getLengthConstraints != null && !getRestrictions(field.getReturnType).getLengthConstraints.isEmpty) {
+                @if(field.getReturnType.isInstanceOf[ConcreteType]) {
+                    @{LengthGenerator.generateLengthCheckerCall(fieldName(field), "value")}
+                } else {
+                    @{LengthGenerator.generateLengthCheckerCall(fieldName(field), "value.getValue()")}
+                }
+            }
+            }
+        }
+            this.@{fieldName(field)} = value;
+            return this;
+        }
+    }
+    @if(augmentField != null) {
+        public @{genType.getName}Builder add@{toFirstUpper(augmentField.getName)}(@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}> augmentationType, @{importedNames.get("augmentFieldReturnType")} augmentation) {
+            if (augmentation == null) {
+                return remove@{toFirstUpper(augmentField.getName)}(augmentationType);
+            }
+
+            if (!(this.@{augmentField.getName} instanceof @{importedNames.get("hashMap")})) {
+                this.@{augmentField.getName} = new @{importedNames.get("hashMap")}<>();
+            }
+
+            this.@{augmentField.getName}.put(augmentationType, augmentation);
+            return this;
+        }
+
+        public @{genType.getName}Builder remove@{toFirstUpper(augmentField.getName)}
+        (@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}> augmentationType) {
+            if (this.@{augmentField.getName} instanceof @{importedNames.get("hashMap")}) {
+                this.@{augmentField.getName}.remove(augmentationType);
+            }
+            return this;
+        }
+    }
+}
+
+@generateGetters(addOverride: Boolean) = {
+    @if(!getterMethods.isEmpty) {
+        @for(property <- getterMethods) {
+            @if(addOverride) {@@Override}
+            @{property}
+        }
+    }
+    @if(augmentField != null) {
+        @@SuppressWarnings("unchecked")
+        @if(addOverride) {@@Override}
+        public <E extends @{importedNames.get("augmentFieldReturnType")}> E get@{toFirstUpper(augmentField.getName)}
+        (@{importedNames.get("class")}<E> augmentationType) {
+            if (augmentationType == null) {
+                throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
+            }
+            return (E) @{augmentField.getName}.get(augmentationType);
+        }
+    }
+}
+
+@generateHashCode() = {
+    @if(!properties.isEmpty || augmentField != null) {
+        private int hash = 0;
+        private volatile boolean hashValid = false;
+
+        @@Override
+        public int hashCode() {
+            if (hashValid) {
+                return hash;
+            }
+
+            final int prime = 31;
+            int result = 1;
+            @for(property <- properties) {
+                @if(property.getReturnType.getName.contains("[")) {
+                    result = prime * result + @{importedNames.get("arrays")}.hashCode(@{fieldName(property)});
+                } else {
+                    result = prime * result + @{importedNames.get("objects")}.hashCode(@{fieldName(property)});
+                }
+            }
+            @if(augmentField != null) {
+                result = prime * result + @{importedNames.get("objects")}.hashCode(@{augmentField.getName});
+            }
+
+            hash = result;
+            hashValid = true;
+            return result;
+        }
+    }
+}
+
+@generateToString() = {
+    @if(properties != null) {
+        @@Override
+        public @{importedNames.get("string")} toString() {
+            @{importedNames.get("stringBuilder")} builder = new @{importedNames.get("stringBuilder")} ("@{genType.getName} [");
+            boolean first = true;
+            @for(property <- properties) {
+                if (@{fieldName(property)} != null) {
+                    if (first) {
+                        first = false;
+                    } else {
+                        builder.append(", ");
+                    }
+                    builder.append("@{fieldName(property)}=");
+                    @if(property.getReturnType.getName.contains("[")) {
+                        builder.append(@{importedNames.get("arrays")}.toString(@{fieldName(property)}));
+                    } else {
+                        builder.append(@{fieldName(property)});
+                    }
+                }
+            }
+            @if(augmentField != null) {
+                if (first) {
+                    first = false;
+                } @{"else"} {
+                    builder.append(", ");
+                }
+                builder.append("@{augmentField.getName}=");
+                builder.append(@{augmentField.getName}.values());
+            }
+            return builder.append(']').toString();
+        }
+    }
+}
+
+@generateEquals() = {
+    @if(!properties.isEmpty || augmentField != null) {
+        @@Override
+        public boolean equals(@{importedNames.get("object")} obj) {
+            if (this == obj) {
+                return true;
+            }
+            if (!(obj instanceof @{importedNames.get("treeNode")})) {
+                return false;
+            }
+            if (!@{importedNames.get("genType")}.class.equals(((@{importedNames.get("treeNode")})obj).getImplementedInterface)) {
+                return false;
+            }
+            @{importedNames.get("genType")} other = (@{importedNames.get("genType")})obj;
+            @for(property <- properties) {
+                @if(property.getReturnType.getName.contains("[")) {
+                    if (!@{importedNames.get("arrays")}.equals(@{fieldName(property)}, other.@{getterMethodName(property)}()))
+                } else {
+                    if (!@{importedNames.get("objects")}.equals(@{fieldName(property)}, other.@{getterMethodName(property)}()))
+                }
+                {
+                    return false;
+                }
+            }
+            @if(augmentField != null) {
+                if (getClass() == obj.getClass()) {
+                    // Simple case: we are comparing against self
+                    @{genType.getName}Impl otherImpl = (@{genType.getName}Impl) obj;
+                    if (!@{importedNames.get("objects")}.equals(@{augmentField.getName}, otherImpl.@{augmentField.getName})) {
+                        return false;
+                    }
+                } @{"else"} {
+                    // Hard case: compare our augments with presence there...
+                    for (@{importedNames.get("map")}.Entry<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}> e : @{augmentField.getName}.entrySet()) {
+                        if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
+                            return false;
+                        }
+                    }
+                    // .. and give the other one the chance to do the same
+                    if (!obj.equals(this)) {
+                        return false;
+                    }
+                }
+            }
+            return true;
+        }
+    }
+}
\ No newline at end of file