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
34 import org.opendaylight.yangtools.concepts.Builder
37 * Template for generating JAVA builder classes.
40 class BuilderTemplate extends BaseTemplate {
43 * Constant with the name of the concrete method.
45 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
48 * Constant with the suffix for builder classes.
50 val static BUILDER = 'Builder'
53 * Constant with the name of the BuilderFor interface
55 val static BUILDERFOR = Builder.simpleName;
58 * Constant with suffix for the classes which are generated from the builder classes.
60 val static IMPL = 'Impl'
63 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
65 var GeneratedProperty augmentField
68 * Set of class attributes (fields) which are derived from the getter methods names
70 val Set<GeneratedProperty> properties
72 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
75 * Constructs new instance of this class.
76 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
78 new(GeneratedType genType) {
80 this.properties = propertiesFromMethods(createMethods)
81 importMap.put(Builder.simpleName, Builder.package.name)
85 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
86 * and all the methods of the implemented interfaces.
88 * @returns set of method signature instances
90 def private Set<MethodSignature> createMethods() {
91 val Set<MethodSignature> methods = new LinkedHashSet();
92 methods.addAll(type.methodDefinitions)
93 collectImplementedMethods(methods, type.implements)
94 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
100 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
101 * and recursively their implemented interfaces.
103 * @param methods set of method signatures
104 * @param implementedIfcs list of implemented interfaces
106 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
107 if (implementedIfcs == null || implementedIfcs.empty) {
110 for (implementedIfc : implementedIfcs) {
111 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
112 val ifc = implementedIfc as GeneratedType
113 methods.addAll(ifc.methodDefinitions)
114 collectImplementedMethods(methods, ifc.implements)
115 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
116 for (m : Augmentable.methods) {
117 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
118 val fullyQualifiedName = m.returnType.name
119 val pkg = fullyQualifiedName.package
120 val name = fullyQualifiedName.name
121 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
122 val refType = new ReferencedTypeImpl(pkg, name)
123 val generic = new ReferencedTypeImpl(type.packageName, type.name)
124 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
125 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
126 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
134 * Returns the first element of the list <code>elements</code>.
136 * @param list of elements
138 def private <E> first(List<E> elements) {
143 * Returns the name of the package from <code>fullyQualifiedName</code>.
145 * @param fullyQualifiedName string with fully qualified type name (package + type)
146 * @return string with the package name
148 def private String getPackage(String fullyQualifiedName) {
149 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
150 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
154 * Returns the name of tye type from <code>fullyQualifiedName</code>
156 * @param fullyQualifiedName string with fully qualified type name (package + type)
157 * @return string with the name of the type
159 def private String getName(String fullyQualifiedName) {
160 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
161 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
165 * Creates set of generated property instances from getter <code>methods</code>.
167 * @param set of method signature instances which should be transformed to list of properties
168 * @return set of generated property instances which represents the getter <code>methods</code>
170 def private propertiesFromMethods(Collection<MethodSignature> methods) {
171 if (methods == null || methods.isEmpty()) {
172 return Collections.emptySet
174 val Set<GeneratedProperty> result = new LinkedHashSet
176 val createdField = m.propertyFromGetter
177 if (createdField != null) {
178 result.add(createdField)
185 * Creates generated property instance from the getter <code>method</code> name and return type.
187 * @param method method signature from which is the method name and return type obtained
188 * @return generated property instance for the getter <code>method</code>
189 * @throws IllegalArgumentException<ul>
190 * <li>if the <code>method</code> equals <code>null</code></li>
191 * <li>if the name of the <code>method</code> equals <code>null</code></li>
192 * <li>if the name of the <code>method</code> is empty</li>
193 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
196 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
197 if (method == null || method.name == null || method.name.empty || method.returnType == null) {
198 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
201 if(Types.BOOLEAN.equals(method.returnType)) {
204 if (method.name.startsWith(prefix)) {
205 val fieldName = method.getName().substring(prefix.length()).toFirstLower
206 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
207 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
208 return tmpGenTO.toInstance.properties.first
213 * Template method which generates JAVA class body for builder class and for IMPL class.
215 * @return string with JAVA source code
218 «wrapToDocumentation(formatDataForJavaDoc(type))»
219 public class «type.name»«BUILDER» implements «BUILDERFOR» <«type.importedName»> {
221 «generateFields(false)»
223 «generateAugmentField(false)»
225 «generateConstructorsFromIfcs(type)»
227 «generateCopyConstructor(false)»
229 «generateMethodFieldsFrom(type)»
231 «generateGetters(false)»
235 public «type.name» build() {
236 return new «type.name»«IMPL»(this);
239 private static final class «type.name»«IMPL» implements «type.name» {
241 «implementedInterfaceGetter»
243 «generateFields(true)»
245 «generateAugmentField(true)»
247 «generateCopyConstructor(true)»
249 «generateGetters(true)»
255 «generateToString(properties)»
262 * Generate default constructor and constructor for every implemented interface from uses statements.
264 def private generateConstructorsFromIfcs(Type type) '''
265 public «type.name»«BUILDER»() {
267 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
268 «val ifc = type as GeneratedType»
269 «FOR impl : ifc.implements»
270 «generateConstructorFromIfc(impl)»
276 * Generate constructor with argument of given type.
278 def private Object generateConstructorFromIfc(Type impl) '''
279 «IF (impl instanceof GeneratedType)»
280 «IF !(impl.methodDefinitions.empty)»
281 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
282 «printConstructorPropertySetter(impl)»
285 «FOR implTypeImplement : impl.implements»
286 «generateConstructorFromIfc(implTypeImplement)»
291 def private Object printConstructorPropertySetter(Type implementedIfc) '''
292 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
293 «val ifc = implementedIfc as GeneratedType»
294 «FOR getter : ifc.methodDefinitions»
295 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
297 «FOR impl : ifc.implements»
298 «printConstructorPropertySetter(impl)»
304 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
306 def private generateMethodFieldsFrom(Type type) '''
307 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
308 «val ifc = type as GeneratedType»
309 «IF ifc.hasImplementsFromUses»
310 «val List<Type> done = ifc.getBaseIfcs»
311 «generateMethodFieldsFromComment(ifc)»
312 public void fieldsFrom(«DataObject.importedName» arg) {
313 boolean isValidArg = false;
314 «FOR impl : ifc.getAllIfcs»
315 «generateIfCheck(impl, done)»
318 throw new IllegalArgumentException(
319 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
328 def private generateMethodFieldsFromComment(GeneratedType type) '''
330 *Set fields from given grouping argument. Valid argument is instance of one of following types:
332 «FOR impl : type.getAllIfcs»
333 * <li>«impl.fullyQualifiedName»</li>
337 * @param arg grouping object
338 * @throws IllegalArgumentException if given argument is none of valid types
343 * Method is used to find out if given type implements any interface from uses.
345 def boolean hasImplementsFromUses(GeneratedType type) {
347 for (impl : type.getAllIfcs) {
348 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
355 def private generateIfCheck(Type impl, List<Type> done) '''
356 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
357 «val implType = impl as GeneratedType»
358 if (arg instanceof «implType.fullyQualifiedName») {
359 «printPropertySetter(implType)»
365 def private printPropertySetter(Type implementedIfc) '''
366 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
367 «val ifc = implementedIfc as GeneratedType»
368 «FOR getter : ifc.methodDefinitions»
369 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
374 private def List<Type> getBaseIfcs(GeneratedType type) {
375 val List<Type> baseIfcs = new ArrayList();
376 for (ifc : type.implements) {
377 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
384 private def Set<Type> getAllIfcs(Type type) {
385 val Set<Type> baseIfcs = new HashSet()
386 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
387 val ifc = type as GeneratedType
388 for (impl : ifc.implements) {
389 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
392 baseIfcs.addAll(impl.getAllIfcs)
398 private def List<String> toListOfNames(Collection<Type> types) {
399 val List<String> names = new ArrayList
401 names.add(type.fullyQualifiedName)
407 * Template method which generates class attributes.
409 * @param boolean value which specify whether field is|isn't final
410 * @return string with class attributes and their types
412 def private generateFields(boolean _final) '''
413 «IF properties !== null»
415 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
416 «val restrictions = f.returnType.restrictions»
417 «IF !_final && restrictions != null»
418 «IF !(restrictions.lengthConstraints.empty)»
419 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_length;
421 «IF !(restrictions.rangeConstraints.empty)»
422 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_range;
429 def private generateAugmentField(boolean isPrivate) '''
430 «IF augmentField != null»
431 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();
436 * Template method which generates setter methods
438 * @return string with the setter methods
440 def private generateSetters() '''
441 «FOR field : properties SEPARATOR '\n'»
442 «val length = field.fieldName + "_length"»
443 «val range = field.fieldName + "_range"»
444 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
445 «generateRestrictions(field, "value", length, range)»
446 this.«field.fieldName» = value;
449 «generateLengthMethod(length, field.returnType, type.name+BUILDER, length)»
450 «generateRangeMethod(range, field.returnType.restrictions, field.returnType, type.name+BUILDER, range)»
452 «IF augmentField != null»
454 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
455 if (augmentation == null) {
456 return remove«augmentField.name.toFirstUpper»(augmentationType);
458 this.«augmentField.name».put(augmentationType, augmentation);
462 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
463 this.«augmentField.name».remove(augmentationType);
469 def generateRestrictions(GeneratedProperty field, String paramName, String lengthGetter, String rangeGetter) '''
470 «val Type type = field.returnType»
471 «IF type instanceof ConcreteType»
472 «createRestrictions(type, paramName, type.name.contains("["), lengthGetter, rangeGetter)»
473 «ELSEIF type instanceof GeneratedTransferObject»
474 «createRestrictions(type, paramName, isArrayType(type as GeneratedTransferObject), lengthGetter, rangeGetter)»
478 def private createRestrictions(Type type, String paramName, boolean isArray, String lengthGetter, String rangeGetter) '''
479 «val restrictions = type.getRestrictions»
480 «IF restrictions !== null»
481 «val boolean isNestedType = !(type instanceof ConcreteType)»
482 «IF !restrictions.lengthConstraints.empty»
483 «generateLengthRestriction(type, paramName, lengthGetter, isNestedType, isArray)»
485 «IF !restrictions.rangeConstraints.empty»
486 «generateRangeRestriction(type, paramName, rangeGetter, isNestedType)»
491 def private generateLengthRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
492 «val restrictions = type.getRestrictions»
493 if («paramName» != null) {
494 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
495 «printLengthConstraint(type, clazz, paramName, isNestedType, isArray)»
496 boolean isValidLength = false;
497 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
498 if (r.contains(_constraint)) {
499 isValidLength = true;
502 if (!isValidLength) {
503 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «getterName»));
508 def private generateRangeRestriction(Type type, String paramName, String getterName, boolean isNestedType) '''
509 if («paramName» != null) {
510 «printRangeConstraint(type, paramName, isNestedType)»
511 boolean isValidRange = false;
512 for («Range.importedName»<«type.importedNumber»> r : «getterName»()) {
513 if (r.contains(_constraint)) {
518 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
523 def private CharSequence generateCopyConstructor(boolean impl) '''
524 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
525 «val allProps = new ArrayList(properties)»
526 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
527 «val keyType = type.getKey»
528 «IF isList && keyType != null»
529 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
530 «Collections.sort(keyProps,
532 return p1.name.compareTo(p2.name)
535 «FOR field : keyProps»
536 «removeProperty(allProps, field.name)»
538 «removeProperty(allProps, "key")»
539 if (base.getKey() == null) {
540 this._key = new «keyType.importedName»(
541 «FOR keyProp : keyProps SEPARATOR ", "»
542 base.«keyProp.getterMethodName»()
545 «FOR field : keyProps»
546 this.«field.fieldName» = base.«field.getterMethodName»();
549 this._key = base.getKey();
550 «FOR field : keyProps»
551 this.«field.fieldName» = _key.«field.getterMethodName»();
555 «FOR field : allProps»
556 this.«field.fieldName» = base.«field.getterMethodName»();
558 «IF augmentField != null»
559 «IF !impl»if (base instanceof «type.name»«IMPL») {«ENDIF»
560 «IF !impl»«type.name»«IMPL» _impl = («type.name»«IMPL») base;«ENDIF»
561 «val prop = if (impl) "base" else "_impl"»
563 switch («prop».«augmentField.name».size()) {
565 this.«augmentField.name» = «Collections.importedName».emptyMap();
568 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = «prop».«augmentField.name».entrySet().iterator().next();
569 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
572 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
575 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
582 private def boolean implementsIfc(GeneratedType type, Type impl) {
583 for (Type ifc : type.implements) {
584 if (ifc.equals(impl)) {
591 private def Type getKey(GeneratedType type) {
592 for (m : type.methodDefinitions) {
593 if ("getKey".equals(m.name)) {
600 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
601 var GeneratedProperty toRemove = null
603 if (p.name.equals(name)) {
607 if (toRemove != null) {
608 props.remove(toRemove);
613 * Template method which generate getter methods for IMPL class.
615 * @return string with getter methods
617 def private generateGetters(boolean addOverride) '''
618 «IF !properties.empty»
619 «FOR field : properties SEPARATOR '\n'»
620 «IF addOverride»@Override«ENDIF»
624 «IF augmentField != null»
626 @SuppressWarnings("unchecked")
627 «IF addOverride»@Override«ENDIF»
628 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
629 if (augmentationType == null) {
630 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
632 return (E) «augmentField.name».get(augmentationType);
638 * Template method which generates the method <code>hashCode()</code>.
640 * @return string with the <code>hashCode()</code> method definition in JAVA format
642 def protected generateHashCode() '''
643 «IF !properties.empty || augmentField != null»
645 public int hashCode() {
646 final int prime = 31;
648 «FOR property : properties»
649 «IF property.returnType.name.contains("[")»
650 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
652 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
655 «IF augmentField != null»
656 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
664 * Template method which generates the method <code>equals()</code>.
666 * @return string with the <code>equals()</code> method definition in JAVA format
668 def protected generateEquals() '''
669 «IF !properties.empty || augmentField != null»
671 public boolean equals(«Object.importedName» obj) {
675 if (!(obj instanceof «DataObject.importedName»)) {
678 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
681 «type.importedName» other = («type.importedName»)obj;
682 «FOR property : properties»
683 «val fieldName = property.fieldName»
684 if («fieldName» == null) {
685 if (other.«property.getterMethodName»() != null) {
688 «IF property.returnType.name.contains("[")»
689 } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
691 } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
696 «IF augmentField != null»
697 if (getClass() == obj.getClass()) {
698 // Simple case: we are comparing against self
699 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
700 «val fieldName = augmentField.name»
701 if («fieldName» == null) {
702 if (otherImpl.«fieldName» != null) {
705 } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
709 // Hard case: compare our augments with presence there...
710 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
711 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
715 // .. and give the other one the chance to do the same
716 if (!obj.equals(this)) {
726 def override generateToString(Collection<GeneratedProperty> properties) '''
727 «IF !(properties === null)»
729 public «String.importedName» toString() {
730 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
731 boolean first = true;
733 «FOR property : properties»
734 if («property.fieldName» != null) {
738 builder.append(", ");
740 builder.append("«property.fieldName»=");
741 «IF property.returnType.name.contains("[")»
742 builder.append(«Arrays.importedName».toString(«property.fieldName»));
744 builder.append(«property.fieldName»);
748 «IF augmentField != null»
752 builder.append(", ");
754 builder.append("«augmentField.name»=");
755 builder.append(«augmentField.name».values());
757 return builder.append(']').toString();
762 override protected getFullyQualifiedName() {
763 '''«type.fullyQualifiedName»Builder'''.toString
766 def implementedInterfaceGetter() '''
767 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
768 return «type.importedName».class;
772 private def createDescription(GeneratedType type) {
774 Class that builds {@link «type.importedName»} instances.
776 @see «type.importedName»
780 override def protected String formatDataForJavaDoc(GeneratedType type) {
781 val typeDescription = createDescription(type)
784 «IF !typeDescription.nullOrEmpty»