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.yangtools.sal.java.api.generator
10 import com.google.common.collect.ImmutableSortedSet
11 import com.google.common.collect.Range
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
22 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
23 import org.opendaylight.yangtools.binding.generator.util.Types
24 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
25 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
26 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
28 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
29 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
30 import org.opendaylight.yangtools.sal.binding.model.api.Type
31 import org.opendaylight.yangtools.yang.binding.Augmentable
32 import org.opendaylight.yangtools.yang.binding.DataObject
33 import org.opendaylight.yangtools.yang.binding.Identifiable
36 * Template for generating JAVA builder classes.
39 class BuilderTemplate extends BaseTemplate {
42 * Constant with the name of the concrete method.
44 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
47 * Constant with the suffix for builder classes.
49 val static BUILDER = 'Builder'
52 * Constant with suffix for the classes which are generated from the builder classes.
54 val static IMPL = 'Impl'
57 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
59 var GeneratedProperty augmentField
62 * Set of class attributes (fields) which are derived from the getter methods names
64 val Set<GeneratedProperty> properties
66 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
69 * Constructs new instance of this class.
70 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
72 new(GeneratedType genType) {
74 this.properties = propertiesFromMethods(createMethods)
78 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
79 * and all the methods of the implemented interfaces.
81 * @returns set of method signature instances
83 def private Set<MethodSignature> createMethods() {
84 val Set<MethodSignature> methods = new LinkedHashSet();
85 methods.addAll(type.methodDefinitions)
86 collectImplementedMethods(methods, type.implements)
87 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
93 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
94 * and recursively their implemented interfaces.
96 * @param methods set of method signatures
97 * @param implementedIfcs list of implemented interfaces
99 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
100 if (implementedIfcs == null || implementedIfcs.empty) {
103 for (implementedIfc : implementedIfcs) {
104 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
105 val ifc = implementedIfc as GeneratedType
106 methods.addAll(ifc.methodDefinitions)
107 collectImplementedMethods(methods, ifc.implements)
108 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
109 for (m : Augmentable.methods) {
110 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
111 val fullyQualifiedName = m.returnType.name
112 val pkg = fullyQualifiedName.package
113 val name = fullyQualifiedName.name
114 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
115 val refType = new ReferencedTypeImpl(pkg, name)
116 val generic = new ReferencedTypeImpl(type.packageName, type.name)
117 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
118 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
119 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
127 * Returns the first element of the list <code>elements</code>.
129 * @param list of elements
131 def private <E> first(List<E> elements) {
136 * Returns the name of the package from <code>fullyQualifiedName</code>.
138 * @param fullyQualifiedName string with fully qualified type name (package + type)
139 * @return string with the package name
141 def private String getPackage(String fullyQualifiedName) {
142 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
143 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
147 * Returns the name of tye type from <code>fullyQualifiedName</code>
149 * @param fullyQualifiedName string with fully qualified type name (package + type)
150 * @return string with the name of the type
152 def private String getName(String fullyQualifiedName) {
153 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
154 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
158 * Creates set of generated property instances from getter <code>methods</code>.
160 * @param set of method signature instances which should be transformed to list of properties
161 * @return set of generated property instances which represents the getter <code>methods</code>
163 def private propertiesFromMethods(Collection<MethodSignature> methods) {
164 if (methods == null || methods.isEmpty()) {
165 return Collections.emptySet
167 val Set<GeneratedProperty> result = new LinkedHashSet
169 val createdField = m.propertyFromGetter
170 if (createdField != null) {
171 result.add(createdField)
178 * Creates generated property instance from the getter <code>method</code> name and return type.
180 * @param method method signature from which is the method name and return type obtained
181 * @return generated property instance for the getter <code>method</code>
182 * @throws IllegalArgumentException<ul>
183 * <li>if the <code>method</code> equals <code>null</code></li>
184 * <li>if the name of the <code>method</code> equals <code>null</code></li>
185 * <li>if the name of the <code>method</code> is empty</li>
186 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
189 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
190 if (method == null || method.name == null || method.name.empty || method.returnType == null) {
191 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
194 if(Types.BOOLEAN.equals(method.returnType)) {
197 if (method.name.startsWith(prefix)) {
198 val fieldName = method.getName().substring(prefix.length()).toFirstLower
199 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
200 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
201 return tmpGenTO.toInstance.properties.first
206 * Template method which generates JAVA class body for builder class and for IMPL class.
208 * @return string with JAVA source code
211 «wrapToDocumentation(formatDataForJavaDoc(type))»
212 public class «type.name»«BUILDER» {
214 «generateFields(false)»
216 «generateAugmentField(true)»
218 «generateConstructorsFromIfcs(type)»
220 «generateCopyConstructor(false)»
222 «generateMethodFieldsFrom(type)»
224 «generateGetters(false)»
228 public «type.name» build() {
229 return new «type.name»«IMPL»(this);
232 private static final class «type.name»«IMPL» implements «type.name» {
234 «implementedInterfaceGetter»
236 «generateFields(true)»
238 «generateAugmentField(false)»
240 «generateCopyConstructor(true)»
242 «generateGetters(true)»
248 «generateToString(properties)»
255 * Generate default constructor and constructor for every implemented interface from uses statements.
257 def private generateConstructorsFromIfcs(Type type) '''
258 public «type.name»«BUILDER»() {
260 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
261 «val ifc = type as GeneratedType»
262 «FOR impl : ifc.implements»
263 «generateConstructorFromIfc(impl)»
269 * Generate constructor with argument of given type.
271 def private Object generateConstructorFromIfc(Type impl) '''
272 «IF (impl instanceof GeneratedType)»
273 «val implType = impl as GeneratedType»
275 «IF !(implType.methodDefinitions.empty)»
276 public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {
277 «printConstructorPropertySetter(implType)»
280 «FOR implTypeImplement : implType.implements»
281 «generateConstructorFromIfc(implTypeImplement)»
286 def private Object printConstructorPropertySetter(Type implementedIfc) '''
287 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
288 «val ifc = implementedIfc as GeneratedType»
289 «FOR getter : ifc.methodDefinitions»
290 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
292 «FOR impl : ifc.implements»
293 «printConstructorPropertySetter(impl)»
299 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
301 def private generateMethodFieldsFrom(Type type) '''
302 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
303 «val ifc = type as GeneratedType»
304 «IF ifc.hasImplementsFromUses»
305 «val List<Type> done = ifc.getBaseIfcs»
306 «generateMethodFieldsFromComment(ifc)»
307 public void fieldsFrom(«DataObject.importedName» arg) {
308 boolean isValidArg = false;
309 «FOR impl : ifc.getAllIfcs»
310 «generateIfCheck(impl, done)»
313 throw new IllegalArgumentException(
314 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
323 def private generateMethodFieldsFromComment(GeneratedType type) '''
325 *Set fields from given grouping argument. Valid argument is instance of one of following types:
327 «FOR impl : type.getAllIfcs»
328 * <li>«impl.fullyQualifiedName»</li>
332 * @param arg grouping object
333 * @throws IllegalArgumentException if given argument is none of valid types
338 * Method is used to find out if given type implements any interface from uses.
340 def boolean hasImplementsFromUses(GeneratedType type) {
342 for (impl : type.getAllIfcs) {
343 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
350 def private generateIfCheck(Type impl, List<Type> done) '''
351 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
352 «val implType = impl as GeneratedType»
353 if (arg instanceof «implType.fullyQualifiedName») {
354 «printPropertySetter(implType)»
360 def private printPropertySetter(Type implementedIfc) '''
361 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
362 «val ifc = implementedIfc as GeneratedType»
363 «FOR getter : ifc.methodDefinitions»
364 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
369 private def List<Type> getBaseIfcs(GeneratedType type) {
370 val List<Type> baseIfcs = new ArrayList();
371 for (ifc : type.implements) {
372 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
379 private def Set<Type> getAllIfcs(Type type) {
380 val Set<Type> baseIfcs = new HashSet()
381 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
382 val ifc = type as GeneratedType
383 for (impl : ifc.implements) {
384 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
387 baseIfcs.addAll(impl.getAllIfcs)
393 private def List<String> toListOfNames(Collection<Type> types) {
394 val List<String> names = new ArrayList
396 names.add(type.fullyQualifiedName)
402 * Template method which generates class attributes.
404 * @param boolean value which specify whether field is|isn't final
405 * @return string with class attributes and their types
407 def private generateFields(boolean _final) '''
408 «IF properties !== null»
410 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
411 «val restrictions = f.returnType.restrictions»
412 «IF !_final && restrictions != null»
413 «IF !(restrictions.lengthConstraints.empty)»
414 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_length;
416 «IF !(restrictions.rangeConstraints.empty)»
417 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_range;
424 def private generateAugmentField(boolean init) '''
425 «IF augmentField != null»
426 private «Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();
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 «val length = field.fieldName + "_length"»
438 «val range = field.fieldName + "_range"»
439 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
440 «generateRestrictions(field, "value", length, range)»
441 this.«field.fieldName» = value;
444 «generateLengthMethod(length, field.returnType, type.name+BUILDER, length)»
445 «generateRangeMethod(range, field.returnType.restrictions, field.returnType, type.name+BUILDER, range)»
447 «IF augmentField != null»
449 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
450 this.«augmentField.name».put(augmentationType, augmentation);
456 def generateRestrictions(GeneratedProperty field, String paramName, String lengthGetter, String rangeGetter) '''
457 «val Type type = field.returnType»
458 «IF type instanceof ConcreteType»
459 «createRestrictions(type, paramName, type.name.contains("["), lengthGetter, rangeGetter)»
460 «ELSEIF type instanceof GeneratedTransferObject»
461 «createRestrictions(type, paramName, isArrayType(type as GeneratedTransferObject), lengthGetter, rangeGetter)»
465 def private createRestrictions(Type type, String paramName, boolean isArray, String lengthGetter, String rangeGetter) '''
466 «val restrictions = type.getRestrictions»
467 «IF restrictions !== null»
468 «val boolean isNestedType = !(type instanceof ConcreteType)»
469 «IF !restrictions.lengthConstraints.empty»
470 «generateLengthRestriction(type, paramName, lengthGetter, isNestedType, isArray)»
472 «IF !restrictions.rangeConstraints.empty»
473 «generateRangeRestriction(type, paramName, rangeGetter, isNestedType)»
478 def private generateLengthRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
479 «val restrictions = type.getRestrictions»
480 if («paramName» != null) {
481 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
482 «printLengthConstraint(type, clazz, paramName, isNestedType, isArray)»
483 boolean isValidLength = false;
484 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
485 if (r.contains(_constraint)) {
486 isValidLength = true;
489 if (!isValidLength) {
490 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «getterName»));
495 def private generateRangeRestriction(Type type, String paramName, String getterName, boolean isNestedType) '''
496 if («paramName» != null) {
497 «printRangeConstraint(type, paramName, isNestedType)»
498 boolean isValidRange = false;
499 for («Range.importedName»<«type.importedNumber»> r : «getterName»()) {
500 if (r.contains(_constraint)) {
505 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
510 def private CharSequence generateCopyConstructor(boolean impl) '''
511 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
512 «val allProps = new ArrayList(properties)»
513 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
514 «val keyType = type.getKey»
515 «IF isList && keyType != null»
516 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
517 «Collections.sort(keyProps,
519 return p1.name.compareTo(p2.name)
522 «FOR field : keyProps»
523 «removeProperty(allProps, field.name)»
525 «removeProperty(allProps, "key")»
526 if (base.getKey() == null) {
527 this._key = new «keyType.importedName»(
528 «FOR keyProp : keyProps SEPARATOR ", "»
529 base.«keyProp.getterMethodName»()
532 «FOR field : keyProps»
533 this.«field.fieldName» = base.«field.getterMethodName»();
536 this._key = base.getKey();
537 «FOR field : keyProps»
538 this.«field.fieldName» = _key.«field.getterMethodName»();
542 «FOR field : allProps»
543 this.«field.fieldName» = base.«field.getterMethodName»();
545 «IF augmentField != null»
546 «IF !impl»if (base instanceof «type.name»«IMPL») {«ENDIF»
547 «IF !impl»«type.name»«IMPL» _impl = («type.name»«IMPL») base;«ENDIF»
548 «val prop = if (impl) "base" else "_impl"»
550 switch («prop».«augmentField.name».size()) {
552 this.«augmentField.name» = «Collections.importedName».emptyMap();
555 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = «prop».«augmentField.name».entrySet().iterator().next();
556 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
559 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
562 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
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»
632 public int hashCode() {
633 final int prime = 31;
635 «FOR property : properties»
636 «IF property.returnType.name.contains("[")»
637 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
639 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
642 «IF augmentField != null»
643 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
651 * Template method which generates the method <code>equals()</code>.
653 * @return string with the <code>equals()</code> method definition in JAVA format
655 def protected generateEquals() '''
656 «IF !properties.empty || augmentField != null»
658 public boolean equals(«Object.importedName» obj) {
662 if (!(obj instanceof «DataObject.importedName»)) {
665 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
668 «type.importedName» other = («type.importedName»)obj;
669 «FOR property : properties»
670 «val fieldName = property.fieldName»
671 if («fieldName» == null) {
672 if (other.«property.getterMethodName»() != null) {
675 «IF property.returnType.name.contains("[")»
676 } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
678 } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
683 «IF augmentField != null»
684 if (getClass() == obj.getClass()) {
685 // Simple case: we are comparing against self
686 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
687 «val fieldName = augmentField.name»
688 if («fieldName» == null) {
689 if (otherImpl.«fieldName» != null) {
692 } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
696 // Hard case: compare our augments with presence there...
697 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
698 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
702 // .. and give the other one the chance to do the same
703 if (!obj.equals(this)) {
713 def override generateToString(Collection<GeneratedProperty> properties) '''
714 «IF !(properties === null)»
716 public «String.importedName» toString() {
717 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
718 boolean first = true;
720 «FOR property : properties»
721 if («property.fieldName» != null) {
725 builder.append(", ");
727 builder.append("«property.fieldName»=");
728 «IF property.returnType.name.contains("[")»
729 builder.append(«Arrays.importedName».toString(«property.fieldName»));
731 builder.append(«property.fieldName»);
735 «IF augmentField != null»
739 builder.append(", ");
741 builder.append("«augmentField.name»=");
742 builder.append(«augmentField.name».values());
744 return builder.append(']').toString();
749 override protected getFullyQualifiedName() {
750 '''«type.fullyQualifiedName»Builder'''.toString
753 def implementedInterfaceGetter() '''
754 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
755 return «type.importedName».class;
759 private def createDescription(GeneratedType type) {
761 Class that builds {@link «type.importedName»} instances.
763 @see «type.importedName»
767 override def protected String formatDataForJavaDoc(GeneratedType type) {
768 val typeDescription = createDescription(type)
771 «IF !typeDescription.nullOrEmpty»