Generate checkers for union member types
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / BuilderTemplate.xtend
index a9cabecd83214bb3a0710b98ba0b4609e482afbd..004a4e5dc5ab8a00fcae8267c15c8f6613e88d8e 100644 (file)
@@ -7,8 +7,14 @@
  */
 package org.opendaylight.mdsal.binding.java.api.generator
 
+import static extension org.apache.commons.text.StringEscapeUtils.escapeJava;
+import static org.opendaylight.yangtools.yang.binding.BindingMapping.AUGMENTATION_FIELD
+import static org.opendaylight.yangtools.yang.binding.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
+
+import com.google.common.base.MoreObjects
 import com.google.common.collect.ImmutableMap
 import com.google.common.collect.ImmutableSortedSet
+import com.google.common.collect.ImmutableList
 import java.util.ArrayList
 import java.util.Arrays
 import java.util.Collection
@@ -20,68 +26,71 @@ import java.util.List
 import java.util.Map
 import java.util.Objects
 import java.util.Set
-import org.opendaylight.mdsal.binding.model.api.ConcreteType
+import java.util.regex.Pattern
 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
 import org.opendaylight.mdsal.binding.model.api.GeneratedType
+import org.opendaylight.mdsal.binding.model.api.JavaTypeName
 import org.opendaylight.mdsal.binding.model.api.MethodSignature
 import org.opendaylight.mdsal.binding.model.api.Type
+import org.opendaylight.mdsal.binding.model.api.ParameterizedType
 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
 import org.opendaylight.mdsal.binding.model.util.Types
-import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
+import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder
+import org.opendaylight.mdsal.binding.model.util.TypeConstants
 import org.opendaylight.yangtools.concepts.Builder
 import org.opendaylight.yangtools.yang.binding.Augmentable
 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
+import org.opendaylight.yangtools.yang.binding.BindingMapping
+import org.opendaylight.yangtools.yang.binding.CodeHelpers
 import org.opendaylight.yangtools.yang.binding.DataObject
 import org.opendaylight.yangtools.yang.binding.Identifiable
-import org.opendaylight.yangtools.yang.binding.CodeHelpers
 
 /**
  * Template for generating JAVA builder classes.
  */
 
 class BuilderTemplate extends BaseTemplate {
-
-    /**
-     * Constant with the name of the concrete method.
-     */
-    val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
-
     /**
      * Constant with the suffix for builder classes.
      */
     val static BUILDER = 'Builder'
 
-    /**
-     * Constant with the name of the BuilderFor interface
-     */
-    val static BUILDERFOR = Builder.simpleName;
-
     /**
      * Constant with suffix for the classes which are generated from the builder classes.
      */
     val static IMPL = 'Impl'
 
     /**
-     * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
+     * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME.
      */
-    var GeneratedProperty augmentField
+    var Type augmentType
 
     /**
-     * Set of class attributes (fields) which are derived from the getter methods names
+     * Set of class attributes (fields) which are derived from the getter methods names.
      */
     val Set<GeneratedProperty> properties
 
-    private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
+    /**
+     * GeneratedType for key type, null if this type does not have a key.
+     */
+    val Type keyType
+
+    static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
 
     /**
      * Constructs new instance of this class.
      * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
      */
     new(GeneratedType genType) {
-        super(genType)
+        super(new TopLevelJavaGeneratedType(builderName(genType), genType), genType)
         this.properties = propertiesFromMethods(createMethods)
-        addImport(Builder.simpleName, Builder.package.name)
+        keyType = genType.key
+    }
+
+    def static builderName(GeneratedType genType) {
+        val name = genType.identifier
+        name.createSibling(name.simpleName + "Builder")
     }
 
     /**
@@ -116,19 +125,11 @@ class BuilderTemplate extends BaseTemplate {
                 methods.addAll(ifc.methodDefinitions)
                 collectImplementedMethods(methods, ifc.implements)
             } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
-                for (m : Augmentable.methods) {
-                    if (m.name == GET_AUGMENTATION_METHOD_NAME) {
-                        val fullyQualifiedName = m.returnType.name
-                        val pkg = fullyQualifiedName.package
-                        val name = fullyQualifiedName.name
-                        val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
-                        val refType = new ReferencedTypeImpl(pkg, name)
-                        val generic = new ReferencedTypeImpl(type.packageName, type.name)
-                        val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
-                        tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
-                        augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
-                    }
-                }
+                val m = Augmentable.getDeclaredMethod(AUGMENTABLE_AUGMENTATION_NAME, Class)
+                val identifier = JavaTypeName.create(m.returnType)
+                val refType = new ReferencedTypeImpl(identifier)
+                val generic = new ReferencedTypeImpl(type.identifier)
+                augmentType = Types.parameterizedTypeFor(refType, generic)
             }
         }
     }
@@ -142,28 +143,6 @@ class BuilderTemplate extends BaseTemplate {
         elements.get(0)
     }
 
-    /**
-     * 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
-     */
-    def private String getPackage(String fullyQualifiedName) {
-        val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
-        return if (lastDotIndex == -1) "" else 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
-     */
-    def private String getName(String fullyQualifiedName) {
-        val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
-        return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
-    }
-
     /**
      * Creates set of generated property instances from getter <code>methods</code>.
      *
@@ -206,13 +185,13 @@ class BuilderTemplate extends BaseTemplate {
         }
         if (method.name.startsWith(prefix)) {
             val fieldName = method.getName().substring(prefix.length()).toFirstLower
-            val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
+            val tmpGenTO = new CodegenGeneratedTOBuilder(JavaTypeName.create("foo", "foo"))
             tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
-            return tmpGenTO.toInstance.properties.first
+            return tmpGenTO.build.properties.first
         }
     }
 
-    override isLocalInnerClass(String importedTypePackageName) {
+    override isLocalInnerClass(JavaTypeName name) {
         // Builders do not have inner types
         return false;
     }
@@ -224,10 +203,12 @@ class BuilderTemplate extends BaseTemplate {
      */
     override body() '''
         «wrapToDocumentation(formatDataForJavaDoc(type))»
-        public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
+        public class «type.name»«BUILDER» implements «Builder.importedName»<«type.importedName»> {
 
             «generateFields(false)»
 
+            «constantsDeclarations()»
+
             «generateAugmentField(false)»
 
             «generateConstructorsFromIfcs(type)»
@@ -240,7 +221,7 @@ class BuilderTemplate extends BaseTemplate {
 
             «generateSetters»
 
-            @Override
+            @«Override.importedName»
             public «type.name» build() {
                 return new «type.name»«IMPL»(this);
             }
@@ -419,78 +400,114 @@ class BuilderTemplate extends BaseTemplate {
                 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
             «ENDFOR»
         «ENDIF»
+        «IF keyType !== null»
+            private«IF _final» final«ENDIF» «keyType.importedName» key;
+        «ENDIF»
     '''
 
     def private generateAugmentField(boolean isPrivate) '''
-        «IF augmentField !== null»
-            «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
+        «IF augmentType !== null»
+            «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> «AUGMENTATION_FIELD» = «Collections.importedName».emptyMap();
         «ENDIF»
     '''
 
+    def private constantsDeclarations() '''
+        «FOR c : type.getConstantDefinitions»
+            «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
+                «val cValue = c.value as Map<String, String>»
+                «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
+                «IF cValue.size == 1»
+                   private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«cValue.keySet.get(0).escapeJava»");
+                   private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«cValue.values.get(0).escapeJava»";
+                «ELSE»
+                   private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
+                   «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
+                   private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
+                   FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
+                «ENDIF»
+            «ELSE»
+                «emitConstant(c)»
+            «ENDIF»
+        «ENDFOR»
+    '''
+
+    def private generateListSetter(GeneratedProperty field, Type actualType) '''
+        «val restrictions = restrictionsForSetter(actualType)»
+        «IF restrictions !== null»
+            «generateCheckers(field, restrictions, actualType)»
+        «ENDIF»
+        public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
+        «IF restrictions !== null»
+            if (values != null) {
+               for («actualType.getFullyQualifiedName» value : values) {
+                   «checkArgument(field, restrictions, actualType, "value")»
+               }
+            }
+        «ENDIF»
+            this.«field.fieldName.toString» = values;
+            return this;
+        }
+
+    '''
+
+    def private generateSetter(GeneratedProperty field, Type actualType) '''
+        «val restrictions = restrictionsForSetter(actualType)»
+        «IF restrictions !== null»
+            «generateCheckers(field, restrictions, actualType)»
+        «ENDIF»
+
+        public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
+        «IF restrictions !== null»
+            if (value != null) {
+                «checkArgument(field, restrictions, actualType, "value")»
+            }
+        «ENDIF»
+            this.«field.fieldName.toString» = value;
+            return this;
+        }
+    '''
+
+    private def Type getActualType(ParameterizedType ptype) {
+        return ptype.getActualTypeArguments.get(0)
+    }
+
     /**
      * Template method which generates setter methods
      *
      * @return string with the setter methods
      */
     def private generateSetters() '''
-        «FOR field : properties SEPARATOR '\n'»
-             «/* FIXME: generate checkers as simple blocks and embed them directly in setters  */»
-             «val restrictions = field.returnType.restrictions»
-             «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
-                    «IF restrictions.rangeConstraint.present»
-                        «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
-                        «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get, this)»
-
-                    «ENDIF»
-                    «IF restrictions.lengthConstraint.present»
-                    «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get, this)»
-
-                    «ENDIF»
-            «ENDIF»
-            public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
-            «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
-                «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
-                if (value != null) {
-                    «IF restrictions.rangeConstraint.present»
-                        «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
-                        «IF field.returnType instanceof ConcreteType»
-                            «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
-                        «ELSE»
-                            «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
-                        «ENDIF»
-                    «ENDIF»
-                    «IF restrictions.lengthConstraint.present»
-                        «IF field.returnType instanceof ConcreteType»
-                            «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
-                         «ELSE»
-                            «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
-                        «ENDIF»
-                    «ENDIF»
-                }
-                «ENDIF»
-            «ENDIF»
-                this.«field.fieldName» = value;
+        «IF keyType !== null»
+            public «type.getName»Builder withKey(final «keyType.importedName» key) {
+                this.key = key;
                 return this;
             }
+        «ENDIF»
+        «FOR property : properties»
+            «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
+                «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
+            «ELSE»
+                «generateSetter(property, property.returnType)»
+            «ENDIF»
         «ENDFOR»
-        «IF augmentField !== null»
 
-            public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
+        «IF augmentType !== null»
+            public «type.name»«BUILDER» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
                 if (augmentationValue == null) {
-                    return remove«augmentField.name.toFirstUpper»(augmentationType);
+                    return remove«AUGMENTATION_FIELD.toFirstUpper»(augmentationType);
                 }
 
-                if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
-                    this.«augmentField.name» = new «HashMap.importedName»<>();
+                if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
+                    this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
                 }
 
-                this.«augmentField.name».put(augmentationType, augmentationValue);
+                this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
                 return this;
             }
 
-            public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
-                if (this.«augmentField.name» instanceof «HashMap.importedName») {
-                    this.«augmentField.name».remove(augmentationType);
+            public «type.name»«BUILDER» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType) {
+                if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
+                    this.«AUGMENTATION_FIELD».remove(augmentationType);
                 }
                 return this;
             }
@@ -501,20 +518,14 @@ class BuilderTemplate extends BaseTemplate {
         «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
             «val allProps = new ArrayList(properties)»
             «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
-            «val keyType = type.getKey»
             «IF isList && keyType !== null»
                 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
-                «Collections.sort(keyProps,
-                    [ p1, p2 |
-                        return p1.name.compareTo(p2.name)
-                    ])
-                »
+                «Collections.sort(keyProps, [ p1, p2 | return p1.name.compareTo(p2.name) ])»
                 «FOR field : keyProps»
                     «removeProperty(allProps, field.name)»
                 «ENDFOR»
-                «removeProperty(allProps, "key")»
-                if (base.getKey() == null) {
-                    this._key = new «keyType.importedName»(
+                if (base.«BindingMapping.IDENTIFIABLE_KEY_NAME»() == null) {
+                    this.key = new «keyType.importedName»(
                         «FOR keyProp : keyProps SEPARATOR ", "»
                             base.«keyProp.getterMethodName»()
                         «ENDFOR»
@@ -523,29 +534,29 @@ class BuilderTemplate extends BaseTemplate {
                         this.«field.fieldName» = base.«field.getterMethodName»();
                     «ENDFOR»
                 } else {
-                    this._key = base.getKey();
+                    this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
                     «FOR field : keyProps»
-                           this.«field.fieldName» = _key.«field.getterMethodName»();
+                           this.«field.fieldName» = key.«field.getterMethodName»();
                     «ENDFOR»
                 }
             «ENDIF»
             «FOR field : allProps»
                 this.«field.fieldName» = base.«field.getterMethodName»();
             «ENDFOR»
-            «IF augmentField !== null»
+            «IF augmentType !== null»
                 «IF impl»
-                    this.«augmentField.name» = «ImmutableMap.importedName».copyOf(base.«augmentField.name»);
+                    this.«AUGMENTATION_FIELD» = «ImmutableMap.importedName».copyOf(base.«AUGMENTATION_FIELD»);
                 «ELSE»
                     if (base instanceof «type.name»«IMPL») {
                         «type.name»«IMPL» impl = («type.name»«IMPL») base;
-                        if (!impl.«augmentField.name».isEmpty()) {
-                            this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
+                        if (!impl.«AUGMENTATION_FIELD».isEmpty()) {
+                            this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(impl.«AUGMENTATION_FIELD»);
                         }
                     } else if (base instanceof «AugmentationHolder.importedName») {
                         @SuppressWarnings("unchecked")
                         «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
                         if (!casted.augmentations().isEmpty()) {
-                            this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
+                            this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(casted.augmentations());
                         }
                     }
                 «ENDIF»
@@ -562,13 +573,12 @@ class BuilderTemplate extends BaseTemplate {
         return false;
     }
 
-    private def Type getKey(GeneratedType type) {
+    private def getKey(GeneratedType type) {
         for (m : type.methodDefinitions) {
-            if ("getKey".equals(m.name)) {
+            if (BindingMapping.IDENTIFIABLE_KEY_NAME.equals(m.name)) {
                 return m.returnType;
             }
         }
-        return null;
     }
 
     private def void removeProperty(Collection<GeneratedProperty> props, String name) {
@@ -589,18 +599,25 @@ class BuilderTemplate extends BaseTemplate {
      * @return string with getter methods
      */
     def private generateGetters(boolean addOverride) '''
+        «IF keyType !== null»
+            «IF addOverride»@«Override.importedName»«ENDIF»
+            public «keyType.importedName» «BindingMapping.IDENTIFIABLE_KEY_NAME»() {
+                return key;
+            }
+
+        «ENDIF»
         «IF !properties.empty»
             «FOR field : properties SEPARATOR '\n'»
-                «IF addOverride»@Override«ENDIF»
+                «IF addOverride»@«Override.importedName»«ENDIF»
                 «field.getterMethod»
             «ENDFOR»
         «ENDIF»
-        «IF augmentField !== null»
+        «IF augmentType !== null»
 
             @SuppressWarnings("unchecked")
-            «IF addOverride»@Override«ENDIF»
-            public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
-                return (E) «augmentField.name».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
+            «IF addOverride»@«Override.importedName»«ENDIF»
+            public <E extends «augmentType.importedName»> E «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E> augmentationType) {
+                return (E) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
             }
         «ENDIF»
     '''
@@ -611,11 +628,11 @@ class BuilderTemplate extends BaseTemplate {
      * @return string with the <code>hashCode()</code> method definition in JAVA format
      */
     def protected generateHashCode() '''
-        «IF !properties.empty || augmentField !== null»
+        «IF !properties.empty || augmentType !== null»
             private int hash = 0;
             private volatile boolean hashValid = false;
 
-            @Override
+            @«Override.importedName»
             public int hashCode() {
                 if (hashValid) {
                     return hash;
@@ -630,8 +647,8 @@ class BuilderTemplate extends BaseTemplate {
                     result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
                     «ENDIF»
                 «ENDFOR»
-                «IF augmentField !== null»
-                    result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
+                «IF augmentType !== null»
+                    result = prime * result + «Objects.importedName».hashCode(«AUGMENTATION_FIELD»);
                 «ENDIF»
 
                 hash = result;
@@ -647,8 +664,8 @@ class BuilderTemplate extends BaseTemplate {
      * @return string with the <code>equals()</code> method definition in JAVA format
      */
     def protected generateEquals() '''
-        «IF !properties.empty || augmentField !== null»
-            @Override
+        «IF !properties.empty || augmentType !== null»
+            @«Override.importedName»
             public boolean equals(«Object.importedName» obj) {
                 if (this == obj) {
                     return true;
@@ -670,18 +687,17 @@ class BuilderTemplate extends BaseTemplate {
                         return false;
                     }
                 «ENDFOR»
-                «IF augmentField !== null»
+                «IF augmentType !== null»
                     if (getClass() == obj.getClass()) {
                         // Simple case: we are comparing against self
                         «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
-                        «val fieldName = augmentField.name»
-                        if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
+                        if (!«Objects.importedName».equals(«AUGMENTATION_FIELD», otherImpl.«AUGMENTATION_FIELD»)) {
                             return false;
                         }
                     } else {
                         // Hard case: compare our augments with presence there...
-                        for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
-                            if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
+                        for («Map.importedName».Entry<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> e : «AUGMENTATION_FIELD».entrySet()) {
+                            if (!e.getValue().equals(other.«AUGMENTABLE_AUGMENTATION_NAME»(e.getKey()))) {
                                 return false;
                             }
                         }
@@ -696,46 +712,24 @@ class BuilderTemplate extends BaseTemplate {
         «ENDIF»
     '''
 
-    def override generateToString(Collection<GeneratedProperty> properties) '''
-        «IF !(properties === null)»
-            @Override
+    override generateToString(Collection<GeneratedProperty> properties) '''
+        «IF properties !== null»
+            @«Override.importedName»
             public «String.importedName» toString() {
-                «String.importedName» name = "«type.name» [";
-                «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
-                «FOR property : properties SEPARATOR "\n    builder.append(\", \");\n}" AFTER "    }\n"»
-                    if («property.fieldName» != null) {
-                        builder.append("«property.fieldName»=");
-                        «IF property.returnType.name.contains("[")»
-                            builder.append(«Arrays.importedName».toString(«property.fieldName»));
-                        «ELSE»
-                            builder.append(«property.fieldName»);
-                        «ENDIF»
+                final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
+                «FOR property : properties»
+                    «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
                 «ENDFOR»
-                «IF augmentField !== null»
-                    «IF !properties.empty»
-                «««Append comma separator only if it's not there already from previous operation»»»
-final int builderLength = builder.length();
-                    final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
-                    if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
-                        builder.append(", ");
-                    }
-                    «ENDIF»
-                    builder.append("«augmentField.name»=");
-                    builder.append(«augmentField.name».values());«"\n"»
-                    return builder.append(']').toString();
-                «ELSE»
-                    «IF properties.empty»
-                    return builder.append(']').toString();
-                    «ELSE»
-            return builder.append(']').toString();
-                    «ENDIF»
+                «IF augmentType !== null»
+                    «CodeHelpers.importedName».appendValue(helper, "«AUGMENTATION_FIELD»", «AUGMENTATION_FIELD».values());
                 «ENDIF»
+                return helper.toString();
             }
         «ENDIF»
     '''
 
     def implementedInterfaceGetter() '''
-    @Override
+    @«Override.importedName»
     public «Class.importedName»<«type.importedName»> getImplementedInterface() {
         return «type.importedName».class;
     }
@@ -749,7 +743,7 @@ final int builderLength = builder.length();
     '''
     }
 
-    override def protected String formatDataForJavaDoc(GeneratedType type) {
+    override protected String formatDataForJavaDoc(GeneratedType type) {
         val typeDescription = createDescription(type)
 
         return '''