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 com.google.common.collect.ImmutableMap
11 import com.google.common.collect.ImmutableSortedSet
12 import java.util.ArrayList
13 import java.util.Arrays
14 import java.util.Collection
15 import java.util.Collections
16 import java.util.HashMap
17 import java.util.HashSet
18 import java.util.LinkedHashSet
21 import java.util.Objects
23 import org.opendaylight.mdsal.binding.model.api.ConcreteType
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
25 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType
27 import org.opendaylight.mdsal.binding.model.api.MethodSignature
28 import org.opendaylight.mdsal.binding.model.api.Type
29 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
30 import org.opendaylight.mdsal.binding.model.util.Types
31 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
32 import org.opendaylight.yangtools.concepts.Builder
33 import org.opendaylight.yangtools.yang.binding.Augmentable
34 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
35 import org.opendaylight.yangtools.yang.binding.DataObject
36 import org.opendaylight.yangtools.yang.binding.Identifiable
37 import org.opendaylight.yangtools.yang.binding.CodeHelpers
40 * Template for generating JAVA builder classes.
43 class BuilderTemplate extends BaseTemplate {
46 * Constant with the name of the concrete method.
48 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
51 * Constant with the suffix for builder classes.
53 val static BUILDER = 'Builder'
56 * Constant with the name of the BuilderFor interface
58 val static BUILDERFOR = Builder.simpleName;
61 * Constant with suffix for the classes which are generated from the builder classes.
63 val static IMPL = 'Impl'
66 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
68 var GeneratedProperty augmentField
71 * Set of class attributes (fields) which are derived from the getter methods names
73 val Set<GeneratedProperty> properties
75 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
78 * Constructs new instance of this class.
79 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
81 new(GeneratedType genType) {
83 this.properties = propertiesFromMethods(createMethods)
84 addImport(Builder.simpleName, Builder.package.name)
88 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
89 * and all the methods of the implemented interfaces.
91 * @returns set of method signature instances
93 def private Set<MethodSignature> createMethods() {
94 val Set<MethodSignature> methods = new LinkedHashSet();
95 methods.addAll(type.methodDefinitions)
96 collectImplementedMethods(methods, type.implements)
97 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
103 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
104 * and recursively their implemented interfaces.
106 * @param methods set of method signatures
107 * @param implementedIfcs list of implemented interfaces
109 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
110 if (implementedIfcs === null || implementedIfcs.empty) {
113 for (implementedIfc : implementedIfcs) {
114 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
115 val ifc = implementedIfc as GeneratedType
116 methods.addAll(ifc.methodDefinitions)
117 collectImplementedMethods(methods, ifc.implements)
118 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
119 for (m : Augmentable.methods) {
120 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
121 val fullyQualifiedName = m.returnType.name
122 val pkg = fullyQualifiedName.package
123 val name = fullyQualifiedName.name
124 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
125 val refType = new ReferencedTypeImpl(pkg, name)
126 val generic = new ReferencedTypeImpl(type.packageName, type.name)
127 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
128 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
129 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
137 * Returns the first element of the list <code>elements</code>.
139 * @param list of elements
141 def private <E> first(List<E> elements) {
146 * Returns the name of the package from <code>fullyQualifiedName</code>.
148 * @param fullyQualifiedName string with fully qualified type name (package + type)
149 * @return string with the package name
151 def private String getPackage(String fullyQualifiedName) {
152 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
153 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
157 * Returns the name of tye type from <code>fullyQualifiedName</code>
159 * @param fullyQualifiedName string with fully qualified type name (package + type)
160 * @return string with the name of the type
162 def private String getName(String fullyQualifiedName) {
163 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
164 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
168 * Creates set of generated property instances from getter <code>methods</code>.
170 * @param set of method signature instances which should be transformed to list of properties
171 * @return set of generated property instances which represents the getter <code>methods</code>
173 def private propertiesFromMethods(Collection<MethodSignature> methods) {
174 if (methods === null || methods.isEmpty()) {
175 return Collections.emptySet
177 val Set<GeneratedProperty> result = new LinkedHashSet
179 val createdField = m.propertyFromGetter
180 if (createdField !== null) {
181 result.add(createdField)
188 * Creates generated property instance from the getter <code>method</code> name and return type.
190 * @param method method signature from which is the method name and return type obtained
191 * @return generated property instance for the getter <code>method</code>
192 * @throws IllegalArgumentException<ul>
193 * <li>if the <code>method</code> equals <code>null</code></li>
194 * <li>if the name of the <code>method</code> equals <code>null</code></li>
195 * <li>if the name of the <code>method</code> is empty</li>
196 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
199 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
200 if (method === null || method.name === null || method.name.empty || method.returnType === null) {
201 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
204 if (Types.BOOLEAN.equals(method.returnType)) {
207 if (method.name.startsWith(prefix)) {
208 val fieldName = method.getName().substring(prefix.length()).toFirstLower
209 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
210 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
211 return tmpGenTO.toInstance.properties.first
215 override isLocalInnerClass(String importedTypePackageName) {
216 // Builders do not have inner types
221 * Template method which generates JAVA class body for builder class and for IMPL class.
223 * @return string with JAVA source code
226 «wrapToDocumentation(formatDataForJavaDoc(type))»
227 public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
229 «generateFields(false)»
231 «generateAugmentField(false)»
233 «generateConstructorsFromIfcs(type)»
235 «generateCopyConstructor(false)»
237 «generateMethodFieldsFrom(type)»
239 «generateGetters(false)»
244 public «type.name» build() {
245 return new «type.name»«IMPL»(this);
248 private static final class «type.name»«IMPL» implements «type.name» {
250 «implementedInterfaceGetter»
252 «generateFields(true)»
254 «generateAugmentField(true)»
256 «generateCopyConstructor(true)»
258 «generateGetters(true)»
264 «generateToString(properties)»
271 * Generate default constructor and constructor for every implemented interface from uses statements.
273 def private generateConstructorsFromIfcs(Type type) '''
274 public «type.name»«BUILDER»() {
276 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
277 «val ifc = type as GeneratedType»
278 «FOR impl : ifc.implements»
279 «generateConstructorFromIfc(impl)»
285 * Generate constructor with argument of given type.
287 def private Object generateConstructorFromIfc(Type impl) '''
288 «IF (impl instanceof GeneratedType)»
289 «IF !(impl.methodDefinitions.empty)»
290 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
291 «printConstructorPropertySetter(impl)»
294 «FOR implTypeImplement : impl.implements»
295 «generateConstructorFromIfc(implTypeImplement)»
300 def private Object printConstructorPropertySetter(Type implementedIfc) '''
301 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
302 «val ifc = implementedIfc as GeneratedType»
303 «FOR getter : ifc.methodDefinitions»
304 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
306 «FOR impl : ifc.implements»
307 «printConstructorPropertySetter(impl)»
313 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
315 def private generateMethodFieldsFrom(Type type) '''
316 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
317 «val ifc = type as GeneratedType»
318 «IF ifc.hasImplementsFromUses»
319 «val List<Type> done = ifc.getBaseIfcs»
320 «generateMethodFieldsFromComment(ifc)»
321 public void fieldsFrom(«DataObject.importedName» arg) {
322 boolean isValidArg = false;
323 «FOR impl : ifc.getAllIfcs»
324 «generateIfCheck(impl, done)»
326 «CodeHelpers.importedName».validValue(isValidArg, arg, "«ifc.getAllIfcs.toListOfNames»");
332 def private generateMethodFieldsFromComment(GeneratedType type) '''
334 * Set fields from given grouping argument. Valid argument is instance of one of following types:
336 «FOR impl : type.getAllIfcs»
337 * <li>«impl.fullyQualifiedName»</li>
341 * @param arg grouping object
342 * @throws IllegalArgumentException if given argument is none of valid types
347 * Method is used to find out if given type implements any interface from uses.
349 def boolean hasImplementsFromUses(GeneratedType type) {
351 for (impl : type.getAllIfcs) {
352 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
359 def private generateIfCheck(Type impl, List<Type> done) '''
360 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
361 «val implType = impl as GeneratedType»
362 if (arg instanceof «implType.fullyQualifiedName») {
363 «printPropertySetter(implType)»
369 def private printPropertySetter(Type implementedIfc) '''
370 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
371 «val ifc = implementedIfc as GeneratedType»
372 «FOR getter : ifc.methodDefinitions»
373 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
378 private def List<Type> getBaseIfcs(GeneratedType type) {
379 val List<Type> baseIfcs = new ArrayList();
380 for (ifc : type.implements) {
381 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
388 private def Set<Type> getAllIfcs(Type type) {
389 val Set<Type> baseIfcs = new HashSet()
390 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
391 val ifc = type as GeneratedType
392 for (impl : ifc.implements) {
393 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
396 baseIfcs.addAll(impl.getAllIfcs)
402 private def List<String> toListOfNames(Collection<Type> types) {
403 val List<String> names = new ArrayList
405 names.add(type.fullyQualifiedName)
411 * Template method which generates class attributes.
413 * @param boolean value which specify whether field is|isn't final
414 * @return string with class attributes and their types
416 def private generateFields(boolean _final) '''
417 «IF properties !== null»
419 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
424 def private generateAugmentField(boolean isPrivate) '''
425 «IF augmentField !== null»
426 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
431 * Template method which generates setter methods
433 * @return string with the setter methods
435 def private generateSetters() '''
436 «FOR field : properties SEPARATOR '\n'»
437 «/* FIXME: generate checkers as simple blocks and embed them directly in setters */»
438 «val restrictions = field.returnType.restrictions»
439 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
440 «IF restrictions.rangeConstraint.present»
441 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
442 «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get)»
445 «IF restrictions.lengthConstraint.present»
446 «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get)»
450 public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
451 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
452 «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
454 «IF restrictions.rangeConstraint.present»
455 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
456 «IF field.returnType instanceof ConcreteType»
457 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
459 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
462 «IF restrictions.lengthConstraint.present»
463 «IF field.returnType instanceof ConcreteType»
464 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
466 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
472 this.«field.fieldName» = value;
476 «IF augmentField !== null»
478 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
479 if (augmentationValue == null) {
480 return remove«augmentField.name.toFirstUpper»(augmentationType);
483 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
484 this.«augmentField.name» = new «HashMap.importedName»<>();
487 this.«augmentField.name».put(augmentationType, augmentationValue);
491 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
492 if (this.«augmentField.name» instanceof «HashMap.importedName») {
493 this.«augmentField.name».remove(augmentationType);
500 def private CharSequence generateCopyConstructor(boolean impl) '''
501 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
502 «val allProps = new ArrayList(properties)»
503 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
504 «val keyType = type.getKey»
505 «IF isList && keyType !== null»
506 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
507 «Collections.sort(keyProps,
509 return p1.name.compareTo(p2.name)
512 «FOR field : keyProps»
513 «removeProperty(allProps, field.name)»
515 «removeProperty(allProps, "key")»
516 if (base.getKey() == null) {
517 this._key = new «keyType.importedName»(
518 «FOR keyProp : keyProps SEPARATOR ", "»
519 base.«keyProp.getterMethodName»()
522 «FOR field : keyProps»
523 this.«field.fieldName» = base.«field.getterMethodName»();
526 this._key = base.getKey();
527 «FOR field : keyProps»
528 this.«field.fieldName» = _key.«field.getterMethodName»();
532 «FOR field : allProps»
533 this.«field.fieldName» = base.«field.getterMethodName»();
535 «IF augmentField !== null»
537 this.«augmentField.name» = «ImmutableMap.importedName».copyOf(base.«augmentField.name»);
539 if (base instanceof «type.name»«IMPL») {
540 «type.name»«IMPL» impl = («type.name»«IMPL») base;
541 if (!impl.«augmentField.name».isEmpty()) {
542 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
544 } else if (base instanceof «AugmentationHolder.importedName») {
545 @SuppressWarnings("unchecked")
546 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
547 if (!casted.augmentations().isEmpty()) {
548 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
556 private def boolean implementsIfc(GeneratedType type, Type impl) {
557 for (Type ifc : type.implements) {
558 if (ifc.equals(impl)) {
565 private def Type getKey(GeneratedType type) {
566 for (m : type.methodDefinitions) {
567 if ("getKey".equals(m.name)) {
574 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
575 var GeneratedProperty toRemove = null
577 if (p.name.equals(name)) {
581 if (toRemove !== null) {
582 props.remove(toRemove);
587 * Template method which generate getter methods for IMPL class.
589 * @return string with getter methods
591 def private generateGetters(boolean addOverride) '''
592 «IF !properties.empty»
593 «FOR field : properties SEPARATOR '\n'»
594 «IF addOverride»@Override«ENDIF»
598 «IF augmentField !== null»
600 @SuppressWarnings("unchecked")
601 «IF addOverride»@Override«ENDIF»
602 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
603 return (E) «augmentField.name».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
609 * Template method which generates the method <code>hashCode()</code>.
611 * @return string with the <code>hashCode()</code> method definition in JAVA format
613 def protected generateHashCode() '''
614 «IF !properties.empty || augmentField !== null»
615 private int hash = 0;
616 private volatile boolean hashValid = false;
619 public int hashCode() {
624 final int prime = 31;
626 «FOR property : properties»
627 «IF property.returnType.name.contains("[")»
628 result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
630 result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
633 «IF augmentField !== null»
634 result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
645 * Template method which generates the method <code>equals()</code>.
647 * @return string with the <code>equals()</code> method definition in JAVA format
649 def protected generateEquals() '''
650 «IF !properties.empty || augmentField !== null»
652 public boolean equals(«Object.importedName» obj) {
656 if (!(obj instanceof «DataObject.importedName»)) {
659 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
662 «type.importedName» other = («type.importedName»)obj;
663 «FOR property : properties»
664 «val fieldName = property.fieldName»
665 «IF property.returnType.name.contains("[")»
666 if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
668 if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
673 «IF augmentField !== null»
674 if (getClass() == obj.getClass()) {
675 // Simple case: we are comparing against self
676 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
677 «val fieldName = augmentField.name»
678 if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
682 // Hard case: compare our augments with presence there...
683 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
684 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
688 // .. and give the other one the chance to do the same
689 if (!obj.equals(this)) {
699 def override generateToString(Collection<GeneratedProperty> properties) '''
700 «IF !(properties === null)»
702 public «String.importedName» toString() {
703 «String.importedName» name = "«type.name» [";
704 «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
705 «FOR property : properties SEPARATOR "\n builder.append(\", \");\n}" AFTER " }\n"»
706 if («property.fieldName» != null) {
707 builder.append("«property.fieldName»=");
708 «IF property.returnType.name.contains("[")»
709 builder.append(«Arrays.importedName».toString(«property.fieldName»));
711 builder.append(«property.fieldName»);
714 «IF augmentField !== null»
715 «IF !properties.empty»
716 «««Append comma separator only if it's not there already from previous operation»»»
717 final int builderLength = builder.length();
718 final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
719 if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
720 builder.append(", ");
723 builder.append("«augmentField.name»=");
724 builder.append(«augmentField.name».values());«"\n"»
725 return builder.append(']').toString();
727 «IF properties.empty»
728 return builder.append(']').toString();
730 return builder.append(']').toString();
737 def implementedInterfaceGetter() '''
739 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
740 return «type.importedName».class;
744 private def createDescription(GeneratedType type) {
746 Class that builds {@link «type.importedName»} instances.
748 @see «type.importedName»
752 override def protected String formatDataForJavaDoc(GeneratedType type) {
753 val typeDescription = createDescription(type)
756 «IF !typeDescription.nullOrEmpty»