2 * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved.
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
8 package org.opendaylight.mdsal.binding.java.api.generator
10 import static extension org.apache.commons.text.StringEscapeUtils.escapeJava;
11 import static org.opendaylight.yangtools.yang.binding.BindingMapping.AUGMENTATION_FIELD
12 import static org.opendaylight.yangtools.yang.binding.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
14 import com.google.common.base.MoreObjects
15 import com.google.common.collect.ImmutableMap
16 import com.google.common.collect.ImmutableSortedSet
17 import com.google.common.collect.ImmutableList
18 import java.util.ArrayList
19 import java.util.Arrays
20 import java.util.Collection
21 import java.util.Collections
22 import java.util.HashMap
23 import java.util.HashSet
24 import java.util.LinkedHashSet
27 import java.util.Objects
29 import java.util.regex.Pattern
30 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
31 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
32 import org.opendaylight.mdsal.binding.model.api.GeneratedType
33 import org.opendaylight.mdsal.binding.model.api.JavaTypeName
34 import org.opendaylight.mdsal.binding.model.api.MethodSignature
35 import org.opendaylight.mdsal.binding.model.api.Type
36 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
37 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
38 import org.opendaylight.mdsal.binding.model.util.Types
39 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder
40 import org.opendaylight.mdsal.binding.model.util.TypeConstants
41 import org.opendaylight.yangtools.concepts.Builder
42 import org.opendaylight.yangtools.yang.binding.Augmentable
43 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
44 import org.opendaylight.yangtools.yang.binding.BindingMapping
45 import org.opendaylight.yangtools.yang.binding.CodeHelpers
46 import org.opendaylight.yangtools.yang.binding.DataObject
47 import org.opendaylight.yangtools.yang.binding.Identifiable
50 * Template for generating JAVA builder classes.
53 class BuilderTemplate extends BaseTemplate {
55 * Constant with the suffix for builder classes.
57 val static BUILDER = 'Builder'
60 * Constant with suffix for the classes which are generated from the builder classes.
62 val static IMPL = 'Impl'
65 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME.
70 * Set of class attributes (fields) which are derived from the getter methods names.
72 val Set<GeneratedProperty> properties
75 * GeneratedType for key type, null if this type does not have a key.
79 static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
82 * Constructs new instance of this class.
83 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
85 new(GeneratedType genType) {
86 super(new TopLevelJavaGeneratedType(builderName(genType), genType), genType)
87 this.properties = propertiesFromMethods(createMethods)
91 def static builderName(GeneratedType genType) {
92 val name = genType.identifier
93 name.createSibling(name.simpleName + "Builder")
97 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
98 * and all the methods of the implemented interfaces.
100 * @returns set of method signature instances
102 def private Set<MethodSignature> createMethods() {
103 val Set<MethodSignature> methods = new LinkedHashSet();
104 methods.addAll(type.methodDefinitions)
105 collectImplementedMethods(methods, type.implements)
106 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
112 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
113 * and recursively their implemented interfaces.
115 * @param methods set of method signatures
116 * @param implementedIfcs list of implemented interfaces
118 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
119 if (implementedIfcs === null || implementedIfcs.empty) {
122 for (implementedIfc : implementedIfcs) {
123 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
124 val ifc = implementedIfc as GeneratedType
125 methods.addAll(ifc.methodDefinitions)
126 collectImplementedMethods(methods, ifc.implements)
127 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
128 val m = Augmentable.getDeclaredMethod(AUGMENTABLE_AUGMENTATION_NAME, Class)
129 val identifier = JavaTypeName.create(m.returnType)
130 val refType = new ReferencedTypeImpl(identifier)
131 val generic = new ReferencedTypeImpl(type.identifier)
132 augmentType = Types.parameterizedTypeFor(refType, generic)
138 * Returns the first element of the list <code>elements</code>.
140 * @param list of elements
142 def private <E> first(List<E> elements) {
147 * Creates set of generated property instances from getter <code>methods</code>.
149 * @param set of method signature instances which should be transformed to list of properties
150 * @return set of generated property instances which represents the getter <code>methods</code>
152 def private propertiesFromMethods(Collection<MethodSignature> methods) {
153 if (methods === null || methods.isEmpty()) {
154 return Collections.emptySet
156 val Set<GeneratedProperty> result = new LinkedHashSet
158 val createdField = m.propertyFromGetter
159 if (createdField !== null) {
160 result.add(createdField)
167 * Creates generated property instance from the getter <code>method</code> name and return type.
169 * @param method method signature from which is the method name and return type obtained
170 * @return generated property instance for the getter <code>method</code>
171 * @throws IllegalArgumentException<ul>
172 * <li>if the <code>method</code> equals <code>null</code></li>
173 * <li>if the name of the <code>method</code> equals <code>null</code></li>
174 * <li>if the name of the <code>method</code> is empty</li>
175 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
178 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
179 if (method === null || method.name === null || method.name.empty || method.returnType === null) {
180 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
183 if (Types.BOOLEAN.equals(method.returnType)) {
186 if (method.name.startsWith(prefix)) {
187 val fieldName = method.getName().substring(prefix.length()).toFirstLower
188 val tmpGenTO = new CodegenGeneratedTOBuilder(JavaTypeName.create("foo", "foo"))
189 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
190 return tmpGenTO.build.properties.first
194 override isLocalInnerClass(JavaTypeName name) {
195 // Builders do not have inner types
200 * Template method which generates JAVA class body for builder class and for IMPL class.
202 * @return string with JAVA source code
205 «wrapToDocumentation(formatDataForJavaDoc(type))»
206 public class «type.name»«BUILDER» implements «Builder.importedName»<«type.importedName»> {
208 «generateFields(false)»
210 «constantsDeclarations()»
212 «generateAugmentField(false)»
214 «generateConstructorsFromIfcs(type)»
216 «generateCopyConstructor(false)»
218 «generateMethodFieldsFrom(type)»
220 «generateGetters(false)»
224 @«Override.importedName»
225 public «type.name» build() {
226 return new «type.name»«IMPL»(this);
229 private static final class «type.name»«IMPL» implements «type.name» {
231 «implementedInterfaceGetter»
233 «generateFields(true)»
235 «generateAugmentField(true)»
237 «generateCopyConstructor(true)»
239 «generateGetters(true)»
245 «generateToString(properties)»
252 * Generate default constructor and constructor for every implemented interface from uses statements.
254 def private generateConstructorsFromIfcs(Type type) '''
255 public «type.name»«BUILDER»() {
257 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
258 «val ifc = type as GeneratedType»
259 «FOR impl : ifc.implements»
260 «generateConstructorFromIfc(impl)»
266 * Generate constructor with argument of given type.
268 def private Object generateConstructorFromIfc(Type impl) '''
269 «IF (impl instanceof GeneratedType)»
270 «IF !(impl.methodDefinitions.empty)»
271 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
272 «printConstructorPropertySetter(impl)»
275 «FOR implTypeImplement : impl.implements»
276 «generateConstructorFromIfc(implTypeImplement)»
281 def private Object printConstructorPropertySetter(Type implementedIfc) '''
282 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
283 «val ifc = implementedIfc as GeneratedType»
284 «FOR getter : ifc.methodDefinitions»
285 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
287 «FOR impl : ifc.implements»
288 «printConstructorPropertySetter(impl)»
294 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
296 def private generateMethodFieldsFrom(Type type) '''
297 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
298 «val ifc = type as GeneratedType»
299 «IF ifc.hasImplementsFromUses»
300 «val List<Type> done = ifc.getBaseIfcs»
301 «generateMethodFieldsFromComment(ifc)»
302 public void fieldsFrom(«DataObject.importedName» arg) {
303 boolean isValidArg = false;
304 «FOR impl : ifc.getAllIfcs»
305 «generateIfCheck(impl, done)»
307 «CodeHelpers.importedName».validValue(isValidArg, arg, "«ifc.getAllIfcs.toListOfNames»");
313 def private generateMethodFieldsFromComment(GeneratedType type) '''
315 * Set fields from given grouping argument. Valid argument is instance of one of following types:
317 «FOR impl : type.getAllIfcs»
318 * <li>«impl.fullyQualifiedName»</li>
322 * @param arg grouping object
323 * @throws IllegalArgumentException if given argument is none of valid types
328 * Method is used to find out if given type implements any interface from uses.
330 def boolean hasImplementsFromUses(GeneratedType type) {
332 for (impl : type.getAllIfcs) {
333 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
340 def private generateIfCheck(Type impl, List<Type> done) '''
341 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
342 «val implType = impl as GeneratedType»
343 if (arg instanceof «implType.fullyQualifiedName») {
344 «printPropertySetter(implType)»
350 def private printPropertySetter(Type implementedIfc) '''
351 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
352 «val ifc = implementedIfc as GeneratedType»
353 «FOR getter : ifc.methodDefinitions»
354 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
359 private def List<Type> getBaseIfcs(GeneratedType type) {
360 val List<Type> baseIfcs = new ArrayList();
361 for (ifc : type.implements) {
362 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
369 private def Set<Type> getAllIfcs(Type type) {
370 val Set<Type> baseIfcs = new HashSet()
371 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
372 val ifc = type as GeneratedType
373 for (impl : ifc.implements) {
374 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
377 baseIfcs.addAll(impl.getAllIfcs)
383 private def List<String> toListOfNames(Collection<Type> types) {
384 val List<String> names = new ArrayList
386 names.add(type.fullyQualifiedName)
392 * Template method which generates class attributes.
394 * @param boolean value which specify whether field is|isn't final
395 * @return string with class attributes and their types
397 def private generateFields(boolean _final) '''
398 «IF properties !== null»
400 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
403 «IF keyType !== null»
404 private«IF _final» final«ENDIF» «keyType.importedName» key;
408 def private generateAugmentField(boolean isPrivate) '''
409 «IF augmentType !== null»
410 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> «AUGMENTATION_FIELD» = «Collections.importedName».emptyMap();
414 def private constantsDeclarations() '''
415 «FOR c : type.getConstantDefinitions»
416 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
417 «val cValue = c.value as Map<String, String>»
418 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
419 «IF cValue.size == 1»
420 private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«cValue.keySet.get(0).escapeJava»");
421 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«cValue.values.get(0).escapeJava»";
423 private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
424 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
425 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
426 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
434 def private generateListSetter(GeneratedProperty field, Type actualType) '''
435 «val restrictions = restrictionsForSetter(actualType)»
436 «IF restrictions !== null»
437 «generateCheckers(field, restrictions, actualType)»
439 public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
440 «IF restrictions !== null»
441 if (values != null) {
442 for («actualType.getFullyQualifiedName» value : values) {
443 «checkArgument(field, restrictions, actualType, "value")»
447 this.«field.fieldName.toString» = values;
453 def private generateSetter(GeneratedProperty field, Type actualType) '''
454 «val restrictions = restrictionsForSetter(actualType)»
455 «IF restrictions !== null»
456 «generateCheckers(field, restrictions, actualType)»
459 public «type.getName»Builder set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
460 «IF restrictions !== null»
462 «checkArgument(field, restrictions, actualType, "value")»
465 this.«field.fieldName.toString» = value;
470 private def Type getActualType(ParameterizedType ptype) {
471 return ptype.getActualTypeArguments.get(0)
475 * Template method which generates setter methods
477 * @return string with the setter methods
479 def private generateSetters() '''
480 «IF keyType !== null»
481 public «type.getName»Builder withKey(final «keyType.importedName» key) {
486 «FOR property : properties»
487 «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
488 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
490 «generateSetter(property, property.returnType)»
494 «IF augmentType !== null»
495 public «type.name»«BUILDER» add«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType, «augmentType.importedName» augmentationValue) {
496 if (augmentationValue == null) {
497 return remove«AUGMENTATION_FIELD.toFirstUpper»(augmentationType);
500 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
501 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
504 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
508 public «type.name»«BUILDER» remove«AUGMENTATION_FIELD.toFirstUpper»(«Class.importedName»<? extends «augmentType.importedName»> augmentationType) {
509 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
510 this.«AUGMENTATION_FIELD».remove(augmentationType);
517 def private CharSequence generateCopyConstructor(boolean impl) '''
518 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
519 «val allProps = new ArrayList(properties)»
520 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
521 «IF isList && keyType !== null»
522 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
523 «Collections.sort(keyProps, [ p1, p2 | return p1.name.compareTo(p2.name) ])»
524 «FOR field : keyProps»
525 «removeProperty(allProps, field.name)»
527 if (base.«BindingMapping.IDENTIFIABLE_KEY_NAME»() == null) {
528 this.key = new «keyType.importedName»(
529 «FOR keyProp : keyProps SEPARATOR ", "»
530 base.«keyProp.getterMethodName»()
533 «FOR field : keyProps»
534 this.«field.fieldName» = base.«field.getterMethodName»();
537 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
538 «FOR field : keyProps»
539 this.«field.fieldName» = key.«field.getterMethodName»();
543 «FOR field : allProps»
544 this.«field.fieldName» = base.«field.getterMethodName»();
546 «IF augmentType !== null»
548 this.«AUGMENTATION_FIELD» = «ImmutableMap.importedName».copyOf(base.«AUGMENTATION_FIELD»);
550 if (base instanceof «type.name»«IMPL») {
551 «type.name»«IMPL» impl = («type.name»«IMPL») base;
552 if (!impl.«AUGMENTATION_FIELD».isEmpty()) {
553 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(impl.«AUGMENTATION_FIELD»);
555 } else if (base instanceof «AugmentationHolder.importedName») {
556 @SuppressWarnings("unchecked")
557 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
558 if (!casted.augmentations().isEmpty()) {
559 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>(casted.augmentations());
567 private def boolean implementsIfc(GeneratedType type, Type impl) {
568 for (Type ifc : type.implements) {
569 if (ifc.equals(impl)) {
576 private def getKey(GeneratedType type) {
577 for (m : type.methodDefinitions) {
578 if (BindingMapping.IDENTIFIABLE_KEY_NAME.equals(m.name)) {
584 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
585 var GeneratedProperty toRemove = null
587 if (p.name.equals(name)) {
591 if (toRemove !== null) {
592 props.remove(toRemove);
597 * Template method which generate getter methods for IMPL class.
599 * @return string with getter methods
601 def private generateGetters(boolean addOverride) '''
602 «IF keyType !== null»
603 «IF addOverride»@«Override.importedName»«ENDIF»
604 public «keyType.importedName» «BindingMapping.IDENTIFIABLE_KEY_NAME»() {
609 «IF !properties.empty»
610 «FOR field : properties SEPARATOR '\n'»
611 «IF addOverride»@«Override.importedName»«ENDIF»
615 «IF augmentType !== null»
617 @SuppressWarnings("unchecked")
618 «IF addOverride»@«Override.importedName»«ENDIF»
619 public <E extends «augmentType.importedName»> E «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E> augmentationType) {
620 return (E) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
626 * Template method which generates the method <code>hashCode()</code>.
628 * @return string with the <code>hashCode()</code> method definition in JAVA format
630 def protected generateHashCode() '''
631 «IF !properties.empty || augmentType !== null»
632 private int hash = 0;
633 private volatile boolean hashValid = false;
635 @«Override.importedName»
636 public int hashCode() {
641 final int prime = 31;
643 «FOR property : properties»
644 «IF property.returnType.name.contains("[")»
645 result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
647 result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
650 «IF augmentType !== null»
651 result = prime * result + «Objects.importedName».hashCode(«AUGMENTATION_FIELD»);
662 * Template method which generates the method <code>equals()</code>.
664 * @return string with the <code>equals()</code> method definition in JAVA format
666 def protected generateEquals() '''
667 «IF !properties.empty || augmentType !== null»
668 @«Override.importedName»
669 public boolean equals(«Object.importedName» obj) {
673 if (!(obj instanceof «DataObject.importedName»)) {
676 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
679 «type.importedName» other = («type.importedName»)obj;
680 «FOR property : properties»
681 «val fieldName = property.fieldName»
682 «IF property.returnType.name.contains("[")»
683 if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
685 if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
690 «IF augmentType !== null»
691 if (getClass() == obj.getClass()) {
692 // Simple case: we are comparing against self
693 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
694 if (!«Objects.importedName».equals(«AUGMENTATION_FIELD», otherImpl.«AUGMENTATION_FIELD»)) {
698 // Hard case: compare our augments with presence there...
699 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentType.importedName»>, «augmentType.importedName»> e : «AUGMENTATION_FIELD».entrySet()) {
700 if (!e.getValue().equals(other.«AUGMENTABLE_AUGMENTATION_NAME»(e.getKey()))) {
704 // .. and give the other one the chance to do the same
705 if (!obj.equals(this)) {
715 override generateToString(Collection<GeneratedProperty> properties) '''
716 «IF properties !== null»
717 @«Override.importedName»
718 public «String.importedName» toString() {
719 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
720 «FOR property : properties»
721 «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
723 «IF augmentType !== null»
724 «CodeHelpers.importedName».appendValue(helper, "«AUGMENTATION_FIELD»", «AUGMENTATION_FIELD».values());
726 return helper.toString();
731 def implementedInterfaceGetter() '''
732 @«Override.importedName»
733 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
734 return «type.importedName».class;
738 private def createDescription(GeneratedType type) {
740 Class that builds {@link «type.importedName»} instances.
742 @see «type.importedName»
746 override protected String formatDataForJavaDoc(GeneratedType type) {
747 val typeDescription = createDescription(type)
750 «IF !typeDescription.nullOrEmpty»