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.ImmutableSortedSet
11 import java.util.ArrayList
12 import java.util.Arrays
13 import java.util.Collection
14 import java.util.Collections
15 import java.util.HashMap
16 import java.util.HashSet
17 import java.util.LinkedHashSet
20 import java.util.Objects
22 import org.opendaylight.mdsal.binding.model.api.ConcreteType
23 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
24 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType
26 import org.opendaylight.mdsal.binding.model.api.MethodSignature
27 import org.opendaylight.mdsal.binding.model.api.Type
28 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
29 import org.opendaylight.mdsal.binding.model.util.Types
30 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
31 import org.opendaylight.yangtools.concepts.Builder
32 import org.opendaylight.yangtools.yang.binding.Augmentable
33 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
34 import org.opendaylight.yangtools.yang.binding.DataObject
35 import org.opendaylight.yangtools.yang.binding.Identifiable
38 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends BaseTemplate {
44 * Constant with the name of the concrete method.
46 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
49 * Constant with the suffix for builder classes.
51 val static BUILDER = 'Builder'
54 * Constant with the name of the BuilderFor interface
56 val static BUILDERFOR = Builder.simpleName;
59 * Constant with suffix for the classes which are generated from the builder classes.
61 val static IMPL = 'Impl'
64 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
66 var GeneratedProperty augmentField
69 * Set of class attributes (fields) which are derived from the getter methods names
71 val Set<GeneratedProperty> properties
73 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
76 * Constructs new instance of this class.
77 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
79 new(GeneratedType genType) {
81 this.properties = propertiesFromMethods(createMethods)
82 importMap.put(Builder.simpleName, Builder.package.name)
86 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
87 * and all the methods of the implemented interfaces.
89 * @returns set of method signature instances
91 def private Set<MethodSignature> createMethods() {
92 val Set<MethodSignature> methods = new LinkedHashSet();
93 methods.addAll(type.methodDefinitions)
94 collectImplementedMethods(methods, type.implements)
95 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
101 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
102 * and recursively their implemented interfaces.
104 * @param methods set of method signatures
105 * @param implementedIfcs list of implemented interfaces
107 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
108 if (implementedIfcs === null || implementedIfcs.empty) {
111 for (implementedIfc : implementedIfcs) {
112 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
113 val ifc = implementedIfc as GeneratedType
114 methods.addAll(ifc.methodDefinitions)
115 collectImplementedMethods(methods, ifc.implements)
116 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
117 for (m : Augmentable.methods) {
118 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
119 val fullyQualifiedName = m.returnType.name
120 val pkg = fullyQualifiedName.package
121 val name = fullyQualifiedName.name
122 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
123 val refType = new ReferencedTypeImpl(pkg, name)
124 val generic = new ReferencedTypeImpl(type.packageName, type.name)
125 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
126 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
127 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
135 * Returns the first element of the list <code>elements</code>.
137 * @param list of elements
139 def private <E> first(List<E> elements) {
144 * Returns the name of the package from <code>fullyQualifiedName</code>.
146 * @param fullyQualifiedName string with fully qualified type name (package + type)
147 * @return string with the package name
149 def private String getPackage(String fullyQualifiedName) {
150 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
151 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
155 * Returns the name of tye type from <code>fullyQualifiedName</code>
157 * @param fullyQualifiedName string with fully qualified type name (package + type)
158 * @return string with the name of the type
160 def private String getName(String fullyQualifiedName) {
161 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
162 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
166 * Creates set of generated property instances from getter <code>methods</code>.
168 * @param set of method signature instances which should be transformed to list of properties
169 * @return set of generated property instances which represents the getter <code>methods</code>
171 def private propertiesFromMethods(Collection<MethodSignature> methods) {
172 if (methods === null || methods.isEmpty()) {
173 return Collections.emptySet
175 val Set<GeneratedProperty> result = new LinkedHashSet
177 val createdField = m.propertyFromGetter
178 if (createdField !== null) {
179 result.add(createdField)
186 * Creates generated property instance from the getter <code>method</code> name and return type.
188 * @param method method signature from which is the method name and return type obtained
189 * @return generated property instance for the getter <code>method</code>
190 * @throws IllegalArgumentException<ul>
191 * <li>if the <code>method</code> equals <code>null</code></li>
192 * <li>if the name of the <code>method</code> equals <code>null</code></li>
193 * <li>if the name of the <code>method</code> is empty</li>
194 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
197 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
198 if (method === null || method.name === null || method.name.empty || method.returnType === null) {
199 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
202 if (Types.BOOLEAN.equals(method.returnType)) {
205 if (method.name.startsWith(prefix)) {
206 val fieldName = method.getName().substring(prefix.length()).toFirstLower
207 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
208 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
209 return tmpGenTO.toInstance.properties.first
213 override isLocalInnerClass(String importedTypePackageName) {
214 // Builders do not have inner types
219 * Template method which generates JAVA class body for builder class and for IMPL class.
221 * @return string with JAVA source code
224 «wrapToDocumentation(formatDataForJavaDoc(type))»
225 public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
227 «generateFields(false)»
229 «generateAugmentField(false)»
231 «generateConstructorsFromIfcs(type)»
233 «generateCopyConstructor(false)»
235 «generateMethodFieldsFrom(type)»
237 «generateGetters(false)»
242 public «type.name» build() {
243 return new «type.name»«IMPL»(this);
246 private static final class «type.name»«IMPL» implements «type.name» {
248 «implementedInterfaceGetter»
250 «generateFields(true)»
252 «generateAugmentField(true)»
254 «generateCopyConstructor(true)»
256 «generateGetters(true)»
262 «generateToString(properties)»
269 * Generate default constructor and constructor for every implemented interface from uses statements.
271 def private generateConstructorsFromIfcs(Type type) '''
272 public «type.name»«BUILDER»() {
274 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
275 «val ifc = type as GeneratedType»
276 «FOR impl : ifc.implements»
277 «generateConstructorFromIfc(impl)»
283 * Generate constructor with argument of given type.
285 def private Object generateConstructorFromIfc(Type impl) '''
286 «IF (impl instanceof GeneratedType)»
287 «IF !(impl.methodDefinitions.empty)»
288 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
289 «printConstructorPropertySetter(impl)»
292 «FOR implTypeImplement : impl.implements»
293 «generateConstructorFromIfc(implTypeImplement)»
298 def private Object printConstructorPropertySetter(Type implementedIfc) '''
299 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
300 «val ifc = implementedIfc as GeneratedType»
301 «FOR getter : ifc.methodDefinitions»
302 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
304 «FOR impl : ifc.implements»
305 «printConstructorPropertySetter(impl)»
311 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
313 def private generateMethodFieldsFrom(Type type) '''
314 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
315 «val ifc = type as GeneratedType»
316 «IF ifc.hasImplementsFromUses»
317 «val List<Type> done = ifc.getBaseIfcs»
318 «generateMethodFieldsFromComment(ifc)»
319 public void fieldsFrom(«DataObject.importedName» arg) {
320 boolean isValidArg = false;
321 «FOR impl : ifc.getAllIfcs»
322 «generateIfCheck(impl, done)»
325 throw new IllegalArgumentException(
326 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
335 def private generateMethodFieldsFromComment(GeneratedType type) '''
337 *Set fields from given grouping argument. Valid argument is instance of one of following types:
339 «FOR impl : type.getAllIfcs»
340 * <li>«impl.fullyQualifiedName»</li>
344 * @param arg grouping object
345 * @throws IllegalArgumentException if given argument is none of valid types
350 * Method is used to find out if given type implements any interface from uses.
352 def boolean hasImplementsFromUses(GeneratedType type) {
354 for (impl : type.getAllIfcs) {
355 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
362 def private generateIfCheck(Type impl, List<Type> done) '''
363 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
364 «val implType = impl as GeneratedType»
365 if (arg instanceof «implType.fullyQualifiedName») {
366 «printPropertySetter(implType)»
372 def private printPropertySetter(Type implementedIfc) '''
373 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
374 «val ifc = implementedIfc as GeneratedType»
375 «FOR getter : ifc.methodDefinitions»
376 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
381 private def List<Type> getBaseIfcs(GeneratedType type) {
382 val List<Type> baseIfcs = new ArrayList();
383 for (ifc : type.implements) {
384 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
391 private def Set<Type> getAllIfcs(Type type) {
392 val Set<Type> baseIfcs = new HashSet()
393 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
394 val ifc = type as GeneratedType
395 for (impl : ifc.implements) {
396 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
399 baseIfcs.addAll(impl.getAllIfcs)
405 private def List<String> toListOfNames(Collection<Type> types) {
406 val List<String> names = new ArrayList
408 names.add(type.fullyQualifiedName)
414 * Template method which generates class attributes.
416 * @param boolean value which specify whether field is|isn't final
417 * @return string with class attributes and their types
419 def private generateFields(boolean _final) '''
420 «IF properties !== null»
422 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
427 def private generateAugmentField(boolean isPrivate) '''
428 «IF augmentField !== null»
429 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
434 * Template method which generates setter methods
436 * @return string with the setter methods
438 def private generateSetters() '''
439 «FOR field : properties SEPARATOR '\n'»
440 «/* FIXME: generate checkers as simple blocks and embed them directly in setters */»
441 «val restrictions = field.returnType.restrictions»
442 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
443 «IF restrictions.rangeConstraint.present»
444 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
445 «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get)»
448 «IF restrictions.lengthConstraint.present»
449 «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get)»
453 public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
454 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
455 «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
457 «IF restrictions.rangeConstraint.present»
458 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
459 «IF field.returnType instanceof ConcreteType»
460 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
462 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
465 «IF restrictions.lengthConstraint.present»
466 «IF field.returnType instanceof ConcreteType»
467 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
469 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
475 this.«field.fieldName» = value;
479 «IF augmentField !== null»
481 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
482 if (augmentationValue == null) {
483 return remove«augmentField.name.toFirstUpper»(augmentationType);
486 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
487 this.«augmentField.name» = new «HashMap.importedName»<>();
490 this.«augmentField.name».put(augmentationType, augmentationValue);
494 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
495 if (this.«augmentField.name» instanceof «HashMap.importedName») {
496 this.«augmentField.name».remove(augmentationType);
503 def private CharSequence generateCopyConstructor(boolean impl) '''
504 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
505 «val allProps = new ArrayList(properties)»
506 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
507 «val keyType = type.getKey»
508 «IF isList && keyType !== null»
509 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
510 «Collections.sort(keyProps,
512 return p1.name.compareTo(p2.name)
515 «FOR field : keyProps»
516 «removeProperty(allProps, field.name)»
518 «removeProperty(allProps, "key")»
519 if (base.getKey() == null) {
520 this._key = new «keyType.importedName»(
521 «FOR keyProp : keyProps SEPARATOR ", "»
522 base.«keyProp.getterMethodName»()
525 «FOR field : keyProps»
526 this.«field.fieldName» = base.«field.getterMethodName»();
529 this._key = base.getKey();
530 «FOR field : keyProps»
531 this.«field.fieldName» = _key.«field.getterMethodName»();
535 «FOR field : allProps»
536 this.«field.fieldName» = base.«field.getterMethodName»();
538 «IF augmentField !== null»
540 switch (base.«augmentField.name».size()) {
542 this.«augmentField.name» = «Collections.importedName».emptyMap();
545 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
546 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
549 this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
552 if (base instanceof «type.name»«IMPL») {
553 «type.name»«IMPL» impl = («type.name»«IMPL») base;
554 if (!impl.«augmentField.name».isEmpty()) {
555 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
557 } else if (base instanceof «AugmentationHolder.importedName») {
558 @SuppressWarnings("unchecked")
559 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
560 if (!casted.augmentations().isEmpty()) {
561 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
569 private def boolean implementsIfc(GeneratedType type, Type impl) {
570 for (Type ifc : type.implements) {
571 if (ifc.equals(impl)) {
578 private def Type getKey(GeneratedType type) {
579 for (m : type.methodDefinitions) {
580 if ("getKey".equals(m.name)) {
587 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
588 var GeneratedProperty toRemove = null
590 if (p.name.equals(name)) {
594 if (toRemove !== null) {
595 props.remove(toRemove);
600 * Template method which generate getter methods for IMPL class.
602 * @return string with getter methods
604 def private generateGetters(boolean addOverride) '''
605 «IF !properties.empty»
606 «FOR field : properties SEPARATOR '\n'»
607 «IF addOverride»@Override«ENDIF»
611 «IF augmentField !== null»
613 @SuppressWarnings("unchecked")
614 «IF addOverride»@Override«ENDIF»
615 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
616 if (augmentationType == null) {
617 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
619 return (E) «augmentField.name».get(augmentationType);
625 * Template method which generates the method <code>hashCode()</code>.
627 * @return string with the <code>hashCode()</code> method definition in JAVA format
629 def protected generateHashCode() '''
630 «IF !properties.empty || augmentField !== null»
631 private int hash = 0;
632 private volatile boolean hashValid = false;
635 public int hashCode() {
640 final int prime = 31;
642 «FOR property : properties»
643 «IF property.returnType.name.contains("[")»
644 result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
646 result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
649 «IF augmentField !== null»
650 result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
661 * Template method which generates the method <code>equals()</code>.
663 * @return string with the <code>equals()</code> method definition in JAVA format
665 def protected generateEquals() '''
666 «IF !properties.empty || augmentField !== null»
668 public boolean equals(«Object.importedName» obj) {
672 if (!(obj instanceof «DataObject.importedName»)) {
675 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
678 «type.importedName» other = («type.importedName»)obj;
679 «FOR property : properties»
680 «val fieldName = property.fieldName»
681 «IF property.returnType.name.contains("[")»
682 if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
684 if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
689 «IF augmentField !== null»
690 if (getClass() == obj.getClass()) {
691 // Simple case: we are comparing against self
692 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
693 «val fieldName = augmentField.name»
694 if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
698 // Hard case: compare our augments with presence there...
699 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
700 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
704 // .. and give the other one the chance to do the same
705 if (!obj.equals(this)) {
715 def override generateToString(Collection<GeneratedProperty> properties) '''
716 «IF !(properties === null)»
718 public «String.importedName» toString() {
719 «String.importedName» name = "«type.name» [";
720 «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
721 «FOR property : properties SEPARATOR "\n builder.append(\", \");\n}" AFTER " }\n"»
722 if («property.fieldName» != null) {
723 builder.append("«property.fieldName»=");
724 «IF property.returnType.name.contains("[")»
725 builder.append(«Arrays.importedName».toString(«property.fieldName»));
727 builder.append(«property.fieldName»);
730 «IF augmentField !== null»
731 «IF !properties.empty»
732 «««Append comma separator only if it's not there already from previous operation»»»
733 final int builderLength = builder.length();
734 final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
735 if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
736 builder.append(", ");
739 builder.append("«augmentField.name»=");
740 builder.append(«augmentField.name».values());«"\n"»
741 return builder.append(']').toString();
743 «IF properties.empty»
744 return builder.append(']').toString();
746 return builder.append(']').toString();
753 def implementedInterfaceGetter() '''
755 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
756 return «type.importedName».class;
760 private def createDescription(GeneratedType type) {
762 Class that builds {@link «type.importedName»} instances.
764 @see «type.importedName»
768 override def protected String formatDataForJavaDoc(GeneratedType type) {
769 val typeDescription = createDescription(type)
772 «IF !typeDescription.nullOrEmpty»