Migrate Optional.get() callers
[mdsal.git] / binding / mdsal-binding-java-api-generator / src / main / java / org / opendaylight / mdsal / binding / java / api / generator / ClassTemplate.xtend
1 /*
2  * Copyright (c) 2014 Cisco Systems, Inc. and others.  All rights reserved.
3  *
4  * This program and the accompanying materials are made available under the
5  * terms of the Eclipse Public License v1.0 which accompanies this distribution,
6  * and is available at http://www.eclipse.org/legal/epl-v10.html
7  */
8 package org.opendaylight.mdsal.binding.java.api.generator
9
10 import static java.util.Objects.requireNonNull
11 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.BINARY_TYPE
12 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.BOOLEAN_TYPE
13 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.DECIMAL64_TYPE
14 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.EMPTY_TYPE
15 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INSTANCE_IDENTIFIER
16 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT16_TYPE
17 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT32_TYPE
18 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT64_TYPE
19 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.INT8_TYPE
20 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.STRING_TYPE
21 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT16_TYPE
22 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT32_TYPE
23 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT64_TYPE
24 import static org.opendaylight.mdsal.binding.model.ri.BaseYangTypes.UINT8_TYPE
25 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.SCALAR_TYPE_OBJECT
26 import static org.opendaylight.mdsal.binding.model.ri.BindingTypes.BITS_TYPE_OBJECT
27 import static org.opendaylight.mdsal.binding.model.ri.Types.STRING;
28 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava
29
30 import com.google.common.base.Preconditions
31 import com.google.common.collect.ImmutableList
32 import com.google.common.collect.ImmutableSet
33 import com.google.common.collect.Lists
34 import edu.umd.cs.findbugs.annotations.SuppressFBWarnings
35 import java.util.ArrayList
36 import java.util.Base64;
37 import java.util.Collection
38 import java.util.Comparator
39 import java.util.List
40 import java.util.Map
41 import java.util.Set
42 import javax.management.ConstructorParameters
43 import org.opendaylight.mdsal.binding.model.api.ConcreteType
44 import org.opendaylight.mdsal.binding.model.api.Constant
45 import org.opendaylight.mdsal.binding.model.api.Enumeration
46 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
47 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
48 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
49 import org.opendaylight.mdsal.binding.model.api.Restrictions
50 import org.opendaylight.mdsal.binding.model.api.Type
51 import org.opendaylight.mdsal.binding.model.ri.TypeConstants
52 import org.opendaylight.mdsal.binding.model.ri.Types
53 import org.opendaylight.yangtools.yang.binding.contract.Naming
54 import org.opendaylight.yangtools.yang.common.Empty
55 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
56
57 /**
58  * Template for generating JAVA class.
59  */
60 class ClassTemplate extends BaseTemplate {
61     static val Comparator<GeneratedProperty> PROP_COMPARATOR = Comparator.comparing([prop | prop.name])
62     static val VALUEOF_TYPES = Set.of(
63         BOOLEAN_TYPE,
64         DECIMAL64_TYPE,
65         INT8_TYPE,
66         INT16_TYPE,
67         INT32_TYPE,
68         INT64_TYPE,
69         UINT8_TYPE,
70         UINT16_TYPE,
71         UINT32_TYPE,
72         UINT64_TYPE)
73     /**
74      * {@code java.lang.Boolean} as a JavaTypeName.
75      */
76     static val BOOLEAN = JavaTypeName.create(Boolean)
77
78     /**
79      * {@code com.google.common.collect.ImmutableSet} as a JavaTypeName.
80      */
81     static val IMMUTABLE_SET = JavaTypeName.create(ImmutableSet)
82
83     protected val List<GeneratedProperty> properties
84     protected val List<GeneratedProperty> finalProperties
85     protected val List<GeneratedProperty> parentProperties
86     protected val List<GeneratedProperty> allProperties
87     protected val Restrictions restrictions
88
89     /**
90      * List of enumeration which are generated as JAVA enum type.
91      */
92     protected val List<Enumeration> enums
93
94     /**
95      * List of constant instances which are generated as JAVA public static final attributes.
96      */
97     protected val List<Constant> consts
98
99     protected val GeneratedTransferObject genTO
100
101     val AbstractRangeGenerator<?> rangeGenerator
102
103     /**
104      * Creates instance of this class with concrete <code>genType</code>.
105      *
106      * @param genType generated transfer object which will be transformed to JAVA class source code
107      */
108     new(GeneratedTransferObject genType) {
109         this(new TopLevelJavaGeneratedType(genType), genType)
110     }
111
112     /**
113      * Creates instance of this class with concrete <code>genType</code>.
114      *
115      * @param genType generated transfer object which will be transformed to JAVA class source code
116      */
117     new(AbstractJavaGeneratedType javaType, GeneratedTransferObject genType) {
118         super(javaType, genType)
119         this.genTO = genType
120         this.properties = genType.properties
121         this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
122         this.parentProperties = GeneratorUtil.getPropertiesOfAllParents(genTO)
123         this.restrictions = genType.restrictions
124
125         val sorted = new ArrayList();
126         sorted.addAll(properties);
127         sorted.addAll(parentProperties);
128         sorted.sort(PROP_COMPARATOR);
129
130         this.allProperties = sorted
131         this.enums = genType.enumerations
132         this.consts = genType.constantDefinitions
133
134         if (restrictions !== null && restrictions.rangeConstraint.present) {
135             rangeGenerator = requireNonNull(AbstractRangeGenerator.forType(TypeUtils.encapsulatedValueType(genType)))
136         } else {
137             rangeGenerator = null
138         }
139     }
140
141     /**
142      * Generates JAVA class source code (class body only).
143      *
144      * @return string with JAVA class body source code
145      */
146     def CharSequence generateAsInnerClass() {
147         return generateBody(true)
148     }
149
150     override protected body() {
151         generateBody(false);
152     }
153
154     /**
155      * Template method which generates class body.
156      *
157      * @param isInnerClass boolean value which specify if generated class is|isn't inner
158      * @return string with class source code in JAVA format
159      */
160     def protected generateBody(boolean isInnerClass) '''
161         «type.formatDataForJavaDoc.wrapToDocumentation»
162         «annotationDeclaration»
163         «IF !isInnerClass»
164             «generatedAnnotation»
165         «ENDIF»
166         «generateClassDeclaration(isInnerClass)» {
167             «suidDeclaration»
168             «innerClassesDeclarations»
169             «enumDeclarations»
170             «constantsDeclarations»
171             «generateFields»
172
173             «IF restrictions !== null»
174                 «IF restrictions.lengthConstraint.present»
175                     «LengthGenerator.generateLengthChecker("_value", TypeUtils.encapsulatedValueType(genTO),
176                         restrictions.lengthConstraint.orElseThrow, this)»
177                 «ENDIF»
178                 «IF restrictions.rangeConstraint.present»
179                     «rangeGenerator.generateRangeChecker("_value", restrictions.rangeConstraint.orElseThrow, this)»
180                 «ENDIF»
181             «ENDIF»
182
183             «constructors»
184
185             «defaultInstance»
186
187             «propertyMethods»
188
189             «IF isBitsTypeObject»
190                 «validNamesAndValues»
191             «ENDIF»
192
193             «generateHashCode»
194
195             «generateEquals»
196
197             «generateToString(genTO.toStringIdentifiers)»
198         }
199
200     '''
201
202     def protected propertyMethods() {
203         if (properties.empty) {
204             return ""
205         }
206         isScalarTypeObject ? scalarTypeObjectValue(properties.get(0)) : defaultProperties
207     }
208
209     def private isScalarTypeObject() {
210         for (impl : genTO.implements) {
211             if (SCALAR_TYPE_OBJECT.identifier.equals(impl.identifier)) {
212                 return true
213             }
214         }
215         return false
216     }
217
218     def private isBitsTypeObject() {
219         var wlk = genTO
220         while (wlk !== null) {
221             for (impl : wlk.implements) {
222                 if (BITS_TYPE_OBJECT.identifier.equals(impl.identifier)) {
223                     return true
224                 }
225             }
226             wlk = wlk.superType
227         }
228         return false
229     }
230
231     def private defaultProperties() '''
232         «FOR field : properties SEPARATOR "\n"»
233             «field.getterMethod»
234             «IF !field.readOnly»
235                 «field.setterMethod»
236             «ENDIF»
237         «ENDFOR»
238     '''
239
240     def private scalarTypeObjectValue(GeneratedProperty field) '''
241         @«OVERRIDE.importedName»
242         public «field.returnType.importedName» «Naming.SCALAR_TYPE_OBJECT_GET_VALUE_NAME»() {
243             return «field.fieldName»«field.cloneCall»;
244         }
245     '''
246
247     def private validNamesAndValues() {
248         for (c : consts) {
249             if (TypeConstants.VALID_NAMES_NAME.equals(c.name)) {
250                 return validNamesAndValues(c.value as BitsTypeDefinition)
251             }
252         }
253         return ""
254     }
255
256     def private validNamesAndValues(BitsTypeDefinition typedef) '''
257
258         @«OVERRIDE.importedName»
259         public «IMMUTABLE_SET.importedName»<«STRING.importedName»> validNames() {
260             return «TypeConstants.VALID_NAMES_NAME»;
261         }
262
263         @«OVERRIDE.importedName»
264         public boolean[] values() {
265             return new boolean[] {
266                     «FOR bit : typedef.bits SEPARATOR ','»
267                         «Naming.getPropertyName(bit.name).getterMethodName»()
268                     «ENDFOR»
269                 };
270         }
271     '''
272
273     /**
274      * Template method which generates inner classes inside this interface.
275      *
276      * @return string with the source code for inner classes in JAVA format
277      */
278     def protected innerClassesDeclarations() '''
279         «IF !type.enclosedTypes.empty»
280             «FOR innerClass : type.enclosedTypes SEPARATOR "\n"»
281                 «generateInnerClass(innerClass)»
282             «ENDFOR»
283         «ENDIF»
284     '''
285
286     def protected constructors() '''
287         «IF genTO.unionType»
288             «genUnionConstructor»
289         «ELSEIF genTO.typedef && allProperties.size == 1 && allProperties.get(0).name.equals(TypeConstants.VALUE_PROP)»
290             «typedefConstructor»
291         «ELSE»
292             «allValuesConstructor»
293         «ENDIF»
294
295         «IF !allProperties.empty»
296             «copyConstructor»
297         «ENDIF»
298         «IF properties.empty && !parentProperties.empty »
299             «parentConstructor»
300         «ENDIF»
301     '''
302
303     def allValuesConstructor() '''
304     public «type.name»(«allProperties.asArgumentsDeclaration») {
305         «IF !parentProperties.empty»
306             super(«parentProperties.asArguments»);
307         «ENDIF»
308         «FOR p : allProperties»
309             «generateRestrictions(type, p.fieldName, p.returnType)»
310         «ENDFOR»
311
312         «FOR p : properties»
313             «val fieldName = p.fieldName»
314             «IF p.returnType.name.endsWith("[]")»
315                 this.«fieldName» = «fieldName» == null ? null : «fieldName».clone();
316             «ELSE»
317                 this.«fieldName» = «fieldName»;
318             «ENDIF»
319         «ENDFOR»
320     }
321     '''
322
323     def private typedefConstructor() '''
324     @«ConstructorParameters.importedName»("«TypeConstants.VALUE_PROP»")
325     public «type.name»(«allProperties.asArgumentsDeclaration») {
326         «IF !parentProperties.empty»
327             super(«parentProperties.asArguments»);
328         «ENDIF»
329         «FOR p : allProperties»
330             «generateRestrictions(type, p.fieldName, p.returnType)»
331         «ENDFOR»
332         «/*
333          * If we have patterns, we need to apply them to the value field. This is a sad consequence of how this code is
334          * structured.
335          */»
336         «CODEHELPERS.importedName».requireValue(_value);
337         «genPatternEnforcer("_value")»
338
339         «FOR p : properties»
340             «val fieldName = p.fieldName»
341             this.«fieldName» = «fieldName»«p.cloneCall»;
342         «ENDFOR»
343     }
344     '''
345
346     def protected genUnionConstructor() '''
347     «FOR p : allProperties»
348         «val List<GeneratedProperty> other = new ArrayList(properties)»
349         «IF other.remove(p)»
350             «genConstructor(p, other)»
351         «ENDIF»
352     «ENDFOR»
353     '''
354
355     def protected genConstructor(GeneratedProperty property, Iterable<GeneratedProperty> other) '''
356     public «type.name»(«property.returnType.importedName + " " + property.name») {
357         «IF !parentProperties.empty»
358             super(«parentProperties.asArguments»);
359         «ENDIF»
360
361         «val fieldName = property.fieldName»
362         «generateRestrictions(type, fieldName, property.returnType)»
363
364         this.«fieldName» = «property.name»;
365         «FOR p : other»
366             this.«p.fieldName» = null;
367         «ENDFOR»
368     }
369     '''
370
371     def private genPatternEnforcer(String ref) '''
372         «FOR c : consts»
373             «IF TypeConstants.PATTERN_CONSTANT_NAME.equals(c.name)»
374             «CODEHELPERS.importedName».checkPattern(«ref», «Constants.MEMBER_PATTERN_LIST», «Constants.MEMBER_REGEX_LIST»);
375             «ENDIF»
376         «ENDFOR»
377     '''
378
379     def private static paramValue(Type returnType, String paramName) {
380         if (returnType instanceof ConcreteType) {
381             return paramName
382         } else {
383             return paramName + ".getValue()"
384         }
385     }
386
387     def generateRestrictions(Type type, String paramName, Type returnType) '''
388         «val restrictions = type.restrictions»
389         «IF restrictions !== null»
390             «IF restrictions.lengthConstraint.present || restrictions.rangeConstraint.present»
391             if («paramName» != null) {
392                 «IF restrictions.lengthConstraint.present»
393                     «LengthGenerator.generateLengthCheckerCall(paramName, paramValue(returnType, paramName))»
394                 «ENDIF»
395                 «IF restrictions.rangeConstraint.present»
396                     «rangeGenerator.generateRangeCheckerCall(paramName, paramValue(returnType, paramName))»
397                 «ENDIF»
398             }
399             «ENDIF»
400         «ENDIF»
401     '''
402
403     def protected copyConstructor() '''
404     /**
405      * Creates a copy from Source Object.
406      *
407      * @param source Source object
408      */
409     public «type.name»(«type.name» source) {
410         «IF !parentProperties.empty»
411             super(source);
412         «ENDIF»
413         «FOR p : properties»
414             «val fieldName = p.fieldName»
415             this.«fieldName» = source.«fieldName»;
416         «ENDFOR»
417     }
418     '''
419
420     def protected parentConstructor() '''
421     /**
422      * Creates a new instance from «genTO.superType.importedName»
423      *
424      * @param source Source object
425      */
426     public «type.name»(«genTO.superType.importedName» source) {
427         super(source);
428         «genPatternEnforcer("getValue()")»
429     }
430     '''
431
432     def protected defaultInstance() '''
433         «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
434             «val prop = allProperties.get(0)»
435             «val propType = prop.returnType»
436             «IF !(INSTANCE_IDENTIFIER.identifier.equals(propType.identifier))»
437             public static «genTO.name» getDefaultInstance(final String defaultValue) {
438                 «IF propType.equals(Types.primitiveBooleanType())»
439                     «bitsArgs»
440                 «ELSEIF VALUEOF_TYPES.contains(propType)»
441                     return new «genTO.name»(«propType.importedName».valueOf(defaultValue));
442                 «ELSEIF STRING_TYPE.equals(propType)»
443                     return new «genTO.name»(defaultValue);
444                 «ELSEIF BINARY_TYPE.equals(propType)»
445                     return new «genTO.name»(«Base64.importedName».getDecoder().decode(defaultValue));
446                 «ELSEIF EMPTY_TYPE.equals(propType)»
447                     «Preconditions.importedName».checkArgument(defaultValue.isEmpty(), "Invalid value %s", defaultValue);
448                     return new «genTO.name»(«Empty.importedName».value());
449                 «ELSE»
450                     return new «genTO.name»(new «propType.importedName»(defaultValue));
451                 «ENDIF»
452             }
453             «ENDIF»
454         «ENDIF»
455     '''
456
457     @SuppressFBWarnings(value = "DLS_DEAD_LOCAL_STORE", justification = "FOR with SEPARATOR, not needing for value")
458     def protected bitsArgs() '''
459         «JU_LIST.importedName»<«STRING.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
460         if (!properties.contains(defaultValue)) {
461             throw new «IAE.importedName»("invalid default parameter");
462         }
463         int i = 0;
464         return new «genTO.name»(
465         «FOR prop : allProperties SEPARATOR ","»
466             properties.get(i++).equals(defaultValue) ? true : false
467         «ENDFOR»
468         );
469     '''
470
471     def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
472         «FOR prop : properties SEPARATOR ","»
473             "«prop.name»"
474         «ENDFOR»
475     '''
476
477     /**
478      * Template method which generates JAVA class declaration.
479      *
480      * @param isInnerClass boolean value which specify if generated class is|isn't inner
481      * @return string with class declaration in JAVA format
482      */
483     def protected generateClassDeclaration(boolean isInnerClass) '''
484         public«
485         IF (isInnerClass)»«
486             " static final "»«
487         ELSEIF (type.abstract)»«
488             " abstract "»«
489         ELSE»«
490             " "»«
491         ENDIF»class «type.name»«
492         IF (genTO.superType !== null)»«
493             " extends "»«genTO.superType.importedName»«
494         ENDIF»
495         «IF (!type.implements.empty)»«
496             " implements "»«
497             FOR type : type.implements SEPARATOR ", "»«
498                 type.importedName»«
499             ENDFOR»«
500         ENDIF
501     »'''
502
503     /**
504      * Template method which generates JAVA enum type.
505      *
506      * @return string with inner enum source code in JAVA format
507      */
508     def protected enumDeclarations() '''
509         «IF !enums.empty»
510             «FOR e : enums SEPARATOR "\n"»
511                 «new EnumTemplate(javaType.getEnclosedType(e.identifier), e).generateAsInnerClass»
512             «ENDFOR»
513         «ENDIF»
514     '''
515
516     def protected suidDeclaration() '''
517         «IF genTO.SUID !== null»
518             private static final long serialVersionUID = «genTO.SUID.value»L;
519         «ENDIF»
520     '''
521
522     def protected annotationDeclaration() '''
523         «IF genTO.getAnnotations !== null»
524             «FOR e : genTO.getAnnotations»
525                 @«e.getName»
526             «ENDFOR»
527         «ENDIF»
528     '''
529
530     /**
531      * Template method which generates JAVA constants.
532      *
533      * @return string with constants in JAVA format
534      */
535     def protected constantsDeclarations() '''
536         «IF !consts.empty»
537             «FOR c : consts»
538                 «IF TypeConstants.PATTERN_CONSTANT_NAME.equals(c.name)»
539                     «val cValue = c.value as Map<String, String>»
540                     «val jurPatternRef = JUR_PATTERN.importedName»
541                     public static final «JU_LIST.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
542                     FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»);
543                     «IF cValue.size == 1»
544                         private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST» = «jurPatternRef».compile(«TypeConstants.PATTERN_CONSTANT_NAME».get(0));
545                         private static final String «Constants.MEMBER_REGEX_LIST» = "«cValue.values.iterator.next.escapeJava»";
546                     «ELSE»
547                         private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST» = «CODEHELPERS.importedName».compilePatterns(«TypeConstants.PATTERN_CONSTANT_NAME»);
548                         private static final String[] «Constants.MEMBER_REGEX_LIST» = { «
549                         FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
550                     «ENDIF»
551                 «ELSEIF TypeConstants.VALID_NAMES_NAME.equals(c.name)»
552                     «val cValue = c.value as BitsTypeDefinition»
553                     «val immutableSet = IMMUTABLE_SET.importedName»
554                     protected static final «immutableSet»<«STRING.importedName»> «TypeConstants.VALID_NAMES_NAME» = «immutableSet».of(«
555                     FOR bit : cValue.bits SEPARATOR ", "»"«bit.name»"«ENDFOR»);
556                 «ELSE»
557                     «emitConstant(c)»
558                 «ENDIF»
559             «ENDFOR»
560         «ENDIF»
561     '''
562
563     /**
564      * Template method which generates JAVA class attributes.
565      *
566      * @return string with the class attributes in JAVA format
567      */
568     def protected generateFields() '''
569         «IF !properties.empty»
570             «FOR f : properties»
571                 private«IF isReadOnly(f)» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
572             «ENDFOR»
573         «ENDIF»
574     '''
575
576     protected def isReadOnly(GeneratedProperty field) {
577         return field.readOnly
578     }
579
580     /**
581      * Template method which generates the method <code>hashCode()</code>.
582      *
583      * @return string with the <code>hashCode()</code> method definition in JAVA format
584      */
585     def protected generateHashCode() {
586         val props = genTO.hashCodeIdentifiers
587         val size = props.size
588         if (size == 0) {
589             return ""
590         }
591         return '''
592             @«OVERRIDE.importedName»
593             public int hashCode() {
594                 «IF size != 1»
595                     final int prime = 31;
596                     int result = 1;
597                     «FOR property : props»
598                         result = prime * result + «property.importedHashCodeUtilClass».hashCode(«property.fieldName»);
599                     «ENDFOR»
600                     return result;
601                 «ELSE»
602                     «val prop = props.get(0)»
603                     «IF prop.returnType.equals(Types.primitiveBooleanType())»
604                         return «BOOLEAN.importedName».hashCode(«prop.fieldName»);
605                     «ELSE»
606                         return «CODEHELPERS.importedName».wrapperHashCode(«prop.fieldName»);
607                     «ENDIF»
608                 «ENDIF»
609             }
610         '''
611     }
612
613     def private importedHashCodeUtilClass(GeneratedProperty prop) {
614         val propType = prop.returnType
615         if (propType.equals(Types.primitiveBooleanType)) {
616             return BOOLEAN.importedName
617         }
618         return propType.importedUtilClass
619     }
620
621     /**
622      * Template method which generates the method <code>equals()</code>.
623      *
624      * @return string with the <code>equals()</code> method definition in JAVA format
625      */
626     def private generateEquals() '''
627         «IF !genTO.equalsIdentifiers.empty»
628             @«OVERRIDE.importedName»
629             public final boolean equals(«OBJECT.importedName» obj) {
630                 return this == obj || obj instanceof «type.name» other
631                     «FOR property : genTO.equalsIdentifiers»
632                         «val fieldName = property.fieldName»
633                         «val type = property.returnType»
634                         «IF type.equals(Types.primitiveBooleanType)»
635                             && «fieldName» == other.«fieldName»«
636                         »«ELSE»
637                             && «type.importedUtilClass».equals(«fieldName», other.«fieldName»)«
638                         »«ENDIF»«
639                     »«ENDFOR»;
640             }
641         «ENDIF»
642     '''
643
644     def private generateToString(Collection<GeneratedProperty> properties) '''
645         «IF !properties.empty»
646             @«OVERRIDE.importedName»
647             public «STRING.importedName» toString() {
648                 final var helper = «MOREOBJECTS.importedName».toStringHelper(«type.importedName».class);
649                 «FOR property : properties»
650                     «CODEHELPERS.importedName».«property.valueAppender»(helper, "«property.name»", «property.fieldName»);
651                 «ENDFOR»
652                 return helper.toString();
653             }
654         «ENDIF»
655     '''
656
657     def private valueAppender(GeneratedProperty prop) {
658         if (prop.returnType.equals(Types.primitiveBooleanType())) {
659             return "appendBit"
660         }
661         return "appendValue"
662     }
663
664     def GeneratedProperty getPropByName(String name) {
665         for (GeneratedProperty prop : allProperties) {
666             if (prop.name.equals(name)) {
667                 return prop;
668             }
669         }
670         return null
671     }
672 }