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 «val implType = impl as GeneratedType»
282 «IF !(implType.methodDefinitions.empty)»
283 public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {
284 «printConstructorPropertySetter(implType)»
287 «FOR implTypeImplement : implType.implements»
288 «generateConstructorFromIfc(implTypeImplement)»
293 def private Object printConstructorPropertySetter(Type implementedIfc) '''
294 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
295 «val ifc = implementedIfc as GeneratedType»
296 «FOR getter : ifc.methodDefinitions»
297 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
299 «FOR impl : ifc.implements»
300 «printConstructorPropertySetter(impl)»
306 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
308 def private generateMethodFieldsFrom(Type type) '''
309 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
310 «val ifc = type as GeneratedType»
311 «IF ifc.hasImplementsFromUses»
312 «val List<Type> done = ifc.getBaseIfcs»
313 «generateMethodFieldsFromComment(ifc)»
314 public void fieldsFrom(«DataObject.importedName» arg) {
315 boolean isValidArg = false;
316 «FOR impl : ifc.getAllIfcs»
317 «generateIfCheck(impl, done)»
320 throw new IllegalArgumentException(
321 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
330 def private generateMethodFieldsFromComment(GeneratedType type) '''
332 *Set fields from given grouping argument. Valid argument is instance of one of following types:
334 «FOR impl : type.getAllIfcs»
335 * <li>«impl.fullyQualifiedName»</li>
339 * @param arg grouping object
340 * @throws IllegalArgumentException if given argument is none of valid types
345 * Method is used to find out if given type implements any interface from uses.
347 def boolean hasImplementsFromUses(GeneratedType type) {
349 for (impl : type.getAllIfcs) {
350 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
357 def private generateIfCheck(Type impl, List<Type> done) '''
358 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
359 «val implType = impl as GeneratedType»
360 if (arg instanceof «implType.fullyQualifiedName») {
361 «printPropertySetter(implType)»
367 def private printPropertySetter(Type implementedIfc) '''
368 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
369 «val ifc = implementedIfc as GeneratedType»
370 «FOR getter : ifc.methodDefinitions»
371 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
376 private def List<Type> getBaseIfcs(GeneratedType type) {
377 val List<Type> baseIfcs = new ArrayList();
378 for (ifc : type.implements) {
379 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
386 private def Set<Type> getAllIfcs(Type type) {
387 val Set<Type> baseIfcs = new HashSet()
388 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
389 val ifc = type as GeneratedType
390 for (impl : ifc.implements) {
391 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
394 baseIfcs.addAll(impl.getAllIfcs)
400 private def List<String> toListOfNames(Collection<Type> types) {
401 val List<String> names = new ArrayList
403 names.add(type.fullyQualifiedName)
409 * Template method which generates class attributes.
411 * @param boolean value which specify whether field is|isn't final
412 * @return string with class attributes and their types
414 def private generateFields(boolean _final) '''
415 «IF properties !== null»
417 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
418 «val restrictions = f.returnType.restrictions»
419 «IF !_final && restrictions != null»
420 «IF !(restrictions.lengthConstraints.empty)»
421 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_length;
423 «IF !(restrictions.rangeConstraints.empty)»
424 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_range;
431 def private generateAugmentField(boolean isPrivate) '''
432 «IF augmentField != null»
433 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();
438 * Template method which generates setter methods
440 * @return string with the setter methods
442 def private generateSetters() '''
443 «FOR field : properties SEPARATOR '\n'»
444 «val length = field.fieldName + "_length"»
445 «val range = field.fieldName + "_range"»
446 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
447 «generateRestrictions(field, "value", length, range)»
448 this.«field.fieldName» = value;
451 «generateLengthMethod(length, field.returnType, type.name+BUILDER, length)»
452 «generateRangeMethod(range, field.returnType.restrictions, field.returnType, type.name+BUILDER, range)»
454 «IF augmentField != null»
456 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
457 if (augmentation == null) {
458 return remove«augmentField.name.toFirstUpper»(augmentationType);
460 this.«augmentField.name».put(augmentationType, augmentation);
464 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
465 this.«augmentField.name».remove(augmentationType);
471 def generateRestrictions(GeneratedProperty field, String paramName, String lengthGetter, String rangeGetter) '''
472 «val Type type = field.returnType»
473 «IF type instanceof ConcreteType»
474 «createRestrictions(type, paramName, type.name.contains("["), lengthGetter, rangeGetter)»
475 «ELSEIF type instanceof GeneratedTransferObject»
476 «createRestrictions(type, paramName, isArrayType(type as GeneratedTransferObject), lengthGetter, rangeGetter)»
480 def private createRestrictions(Type type, String paramName, boolean isArray, String lengthGetter, String rangeGetter) '''
481 «val restrictions = type.getRestrictions»
482 «IF restrictions !== null»
483 «val boolean isNestedType = !(type instanceof ConcreteType)»
484 «IF !restrictions.lengthConstraints.empty»
485 «generateLengthRestriction(type, paramName, lengthGetter, isNestedType, isArray)»
487 «IF !restrictions.rangeConstraints.empty»
488 «generateRangeRestriction(type, paramName, rangeGetter, isNestedType)»
493 def private generateLengthRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
494 «val restrictions = type.getRestrictions»
495 if («paramName» != null) {
496 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
497 «printLengthConstraint(type, clazz, paramName, isNestedType, isArray)»
498 boolean isValidLength = false;
499 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
500 if (r.contains(_constraint)) {
501 isValidLength = true;
504 if (!isValidLength) {
505 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «getterName»));
510 def private generateRangeRestriction(Type type, String paramName, String getterName, boolean isNestedType) '''
511 if («paramName» != null) {
512 «printRangeConstraint(type, paramName, isNestedType)»
513 boolean isValidRange = false;
514 for («Range.importedName»<«type.importedNumber»> r : «getterName»()) {
515 if (r.contains(_constraint)) {
520 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
525 def private CharSequence generateCopyConstructor(boolean impl) '''
526 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
527 «val allProps = new ArrayList(properties)»
528 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
529 «val keyType = type.getKey»
530 «IF isList && keyType != null»
531 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
532 «Collections.sort(keyProps,
534 return p1.name.compareTo(p2.name)
537 «FOR field : keyProps»
538 «removeProperty(allProps, field.name)»
540 «removeProperty(allProps, "key")»
541 if (base.getKey() == null) {
542 this._key = new «keyType.importedName»(
543 «FOR keyProp : keyProps SEPARATOR ", "»
544 base.«keyProp.getterMethodName»()
547 «FOR field : keyProps»
548 this.«field.fieldName» = base.«field.getterMethodName»();
551 this._key = base.getKey();
552 «FOR field : keyProps»
553 this.«field.fieldName» = _key.«field.getterMethodName»();
557 «FOR field : allProps»
558 this.«field.fieldName» = base.«field.getterMethodName»();
560 «IF augmentField != null»
561 «IF !impl»if (base instanceof «type.name»«IMPL») {«ENDIF»
562 «IF !impl»«type.name»«IMPL» _impl = («type.name»«IMPL») base;«ENDIF»
563 «val prop = if (impl) "base" else "_impl"»
565 switch («prop».«augmentField.name».size()) {
567 this.«augmentField.name» = «Collections.importedName».emptyMap();
570 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = «prop».«augmentField.name».entrySet().iterator().next();
571 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
574 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
577 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
584 private def boolean implementsIfc(GeneratedType type, Type impl) {
585 for (Type ifc : type.implements) {
586 if (ifc.equals(impl)) {
593 private def Type getKey(GeneratedType type) {
594 for (m : type.methodDefinitions) {
595 if ("getKey".equals(m.name)) {
602 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
603 var GeneratedProperty toRemove = null
605 if (p.name.equals(name)) {
609 if (toRemove != null) {
610 props.remove(toRemove);
615 * Template method which generate getter methods for IMPL class.
617 * @return string with getter methods
619 def private generateGetters(boolean addOverride) '''
620 «IF !properties.empty»
621 «FOR field : properties SEPARATOR '\n'»
622 «IF addOverride»@Override«ENDIF»
626 «IF augmentField != null»
628 @SuppressWarnings("unchecked")
629 «IF addOverride»@Override«ENDIF»
630 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
631 if (augmentationType == null) {
632 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
634 return (E) «augmentField.name».get(augmentationType);
640 * Template method which generates the method <code>hashCode()</code>.
642 * @return string with the <code>hashCode()</code> method definition in JAVA format
644 def protected generateHashCode() '''
645 «IF !properties.empty || augmentField != null»
647 public int hashCode() {
648 final int prime = 31;
650 «FOR property : properties»
651 «IF property.returnType.name.contains("[")»
652 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
654 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
657 «IF augmentField != null»
658 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
666 * Template method which generates the method <code>equals()</code>.
668 * @return string with the <code>equals()</code> method definition in JAVA format
670 def protected generateEquals() '''
671 «IF !properties.empty || augmentField != null»
673 public boolean equals(«Object.importedName» obj) {
677 if (!(obj instanceof «DataObject.importedName»)) {
680 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
683 «type.importedName» other = («type.importedName»)obj;
684 «FOR property : properties»
685 «val fieldName = property.fieldName»
686 if («fieldName» == null) {
687 if (other.«property.getterMethodName»() != null) {
690 «IF property.returnType.name.contains("[")»
691 } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
693 } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
698 «IF augmentField != null»
699 if (getClass() == obj.getClass()) {
700 // Simple case: we are comparing against self
701 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
702 «val fieldName = augmentField.name»
703 if («fieldName» == null) {
704 if (otherImpl.«fieldName» != null) {
707 } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
711 // Hard case: compare our augments with presence there...
712 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
713 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
717 // .. and give the other one the chance to do the same
718 if (!obj.equals(this)) {
728 def override generateToString(Collection<GeneratedProperty> properties) '''
729 «IF !(properties === null)»
731 public «String.importedName» toString() {
732 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
733 boolean first = true;
735 «FOR property : properties»
736 if («property.fieldName» != null) {
740 builder.append(", ");
742 builder.append("«property.fieldName»=");
743 «IF property.returnType.name.contains("[")»
744 builder.append(«Arrays.importedName».toString(«property.fieldName»));
746 builder.append(«property.fieldName»);
750 «IF augmentField != null»
754 builder.append(", ");
756 builder.append("«augmentField.name»=");
757 builder.append(«augmentField.name».values());
759 return builder.append(']').toString();
764 override protected getFullyQualifiedName() {
765 '''«type.fullyQualifiedName»Builder'''.toString
768 def implementedInterfaceGetter() '''
769 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
770 return «type.importedName».class;
774 private def createDescription(GeneratedType type) {
776 Class that builds {@link «type.importedName»} instances.
778 @see «type.importedName»
782 override def protected String formatDataForJavaDoc(GeneratedType type) {
783 val typeDescription = createDescription(type)
786 «IF !typeDescription.nullOrEmpty»