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 java.util.Arrays;
11 import java.util.LinkedHashSet
15 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
16 import org.opendaylight.yangtools.binding.generator.util.Types
17 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
18 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
19 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
20 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
21 import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature
22 import org.opendaylight.yangtools.sal.binding.model.api.Type
23 import org.opendaylight.yangtools.yang.binding.Augmentable
24 import static org.opendaylight.yangtools.binding.generator.util.Types.*
25 import java.util.HashMap
26 import java.util.Collections
27 import org.opendaylight.yangtools.yang.binding.DataObject
28 import java.util.ArrayList
29 import java.util.HashSet
30 import java.util.Collection
31 import org.opendaylight.yangtools.yang.binding.Identifiable
32 import com.google.common.collect.Range
33 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
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
67 * Constructs new instance of this class.
68 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
70 new(GeneratedType genType) {
72 this.properties = propertiesFromMethods(createMethods)
76 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
77 * and all the methods of the implemented interfaces.
79 * @returns set of method signature instances
81 def private Set<MethodSignature> createMethods() {
82 val Set<MethodSignature> methods = new LinkedHashSet
83 methods.addAll(type.methodDefinitions)
84 collectImplementedMethods(methods, type.implements)
89 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
90 * and recursivelly their implemented interfaces.
92 * @param methods set of method signatures
93 * @param implementedIfcs list of implemented interfaces
95 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
96 if (implementedIfcs == null || implementedIfcs.empty) {
99 for (implementedIfc : implementedIfcs) {
100 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
101 val ifc = implementedIfc as GeneratedType
102 methods.addAll(ifc.methodDefinitions)
103 collectImplementedMethods(methods, ifc.implements)
104 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
105 for (m : Augmentable.methods) {
106 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
107 val fullyQualifiedName = m.returnType.name
108 val pkg = fullyQualifiedName.package
109 val name = fullyQualifiedName.name
110 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
111 val refType = new ReferencedTypeImpl(pkg, name)
112 val generic = new ReferencedTypeImpl(type.packageName, type.name)
113 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
114 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
115 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
123 * Returns the first element of the list <code>elements</code>.
125 * @param list of elements
127 def private <E> first(List<E> elements) {
132 * Returns the name of the package from <code>fullyQualifiedName</code>.
134 * @param fullyQualifiedName string with fully qualified type name (package + type)
135 * @return string with the package name
137 def private String getPackage(String fullyQualifiedName) {
138 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
139 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
143 * Returns the name of tye type from <code>fullyQualifiedName</code>
145 * @param fullyQualifiedName string with fully qualified type name (package + type)
146 * @return string with the name of the type
148 def private String getName(String fullyQualifiedName) {
149 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
150 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
154 * Creates set of generated property instances from getter <code>methods</code>.
156 * @param set of method signature instances which should be transformed to list of properties
157 * @return set of generated property instances which represents the getter <code>methods</code>
159 def private propertiesFromMethods(Collection<MethodSignature> methods) {
160 if (methods == null || methods.isEmpty()) {
161 return Collections.emptySet
163 val Set<GeneratedProperty> result = new LinkedHashSet
165 val createdField = m.propertyFromGetter
166 if (createdField != null) {
167 result.add(createdField)
174 * Creates generated property instance from the getter <code>method</code> name and return type.
176 * @param method method signature from which is the method name and return type obtained
177 * @return generated property instance for the getter <code>method</code>
178 * @throws IllegalArgumentException<ul>
179 * <li>if the <code>method</code> equals <code>null</code></li>
180 * <li>if the name of the <code>method</code> equals <code>null</code></li>
181 * <li>if the name of the <code>method</code> is empty</li>
182 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
185 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
186 if (method == null || method.name == null || method.name.empty || method.returnType == null) {
187 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
190 if(BOOLEAN.equals(method.returnType)) {
193 if (method.name.startsWith(prefix)) {
194 val fieldName = method.getName().substring(prefix.length()).toFirstLower
195 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
196 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
197 return tmpGenTO.toInstance.properties.first
202 * Template method which generates JAVA class body for builder class and for IMPL class.
204 * @return string with JAVA source code
208 public class «type.name»«BUILDER» {
210 «generateFields(false)»
212 «generateAugmentField(true)»
214 «generateConstructorsFromIfcs(type)»
216 «generateMethodFieldsFrom(type)»
218 «generateGetters(false)»
222 public «type.name» build() {
223 return new «type.name»«IMPL»(this);
226 private static final class «type.name»«IMPL» implements «type.name» {
228 «implementedInterfaceGetter»
230 «generateFields(true)»
232 «generateAugmentField(false)»
234 «generateConstructor»
236 «generateGetters(true)»
242 «generateToString(properties)»
249 * Generate default constructor and constructor for every implemented interface from uses statements.
251 def private generateConstructorsFromIfcs(Type type) '''
252 public «type.name»«BUILDER»() {
254 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
255 «val ifc = type as GeneratedType»
256 «FOR impl : ifc.implements»
257 «generateConstructorFromIfc(impl)»
263 * Generate constructor with argument of given type.
265 def private Object generateConstructorFromIfc(Type impl) '''
266 «IF (impl instanceof GeneratedType)»
267 «val implType = impl as GeneratedType»
269 «IF !(implType.methodDefinitions.empty)»
270 public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {
271 «printConstructorPropertySetter(implType)»
274 «FOR implTypeImplement : implType.implements»
275 «generateConstructorFromIfc(implTypeImplement)»
280 def private Object printConstructorPropertySetter(Type implementedIfc) '''
281 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
282 «val ifc = implementedIfc as GeneratedType»
283 «FOR getter : ifc.methodDefinitions»
284 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
286 «FOR impl : ifc.implements»
287 «printConstructorPropertySetter(impl)»
293 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
295 def private generateMethodFieldsFrom(Type type) '''
296 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
297 «val ifc = type as GeneratedType»
298 «IF ifc.hasImplementsFromUses»
299 «val List<Type> done = ifc.getBaseIfcs»
300 «generateMethodFieldsFromComment(ifc)»
301 public void fieldsFrom(«DataObject.importedName» arg) {
302 boolean isValidArg = false;
303 «FOR impl : ifc.getAllIfcs»
304 «generateIfCheck(impl, done)»
307 throw new IllegalArgumentException(
308 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
317 def private generateMethodFieldsFromComment(GeneratedType type) '''
319 Set fields from given grouping argument. Valid argument is instance of one of following types:
321 «FOR impl : type.getAllIfcs»
322 * <li>«impl.fullyQualifiedName»</li>
326 * @param arg grouping object
327 * @throws IllegalArgumentException if given argument is none of valid types
332 * Method is used to find out if given type implements any interface from uses.
334 def boolean hasImplementsFromUses(GeneratedType type) {
336 for (impl : type.getAllIfcs) {
337 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
344 def private generateIfCheck(Type impl, List<Type> done) '''
345 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
346 «val implType = impl as GeneratedType»
347 if (arg instanceof «implType.fullyQualifiedName») {
348 «printPropertySetter(implType)»
354 def private printPropertySetter(Type implementedIfc) '''
355 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
356 «val ifc = implementedIfc as GeneratedType»
357 «FOR getter : ifc.methodDefinitions»
358 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
363 private def List<Type> getBaseIfcs(GeneratedType type) {
364 val List<Type> baseIfcs = new ArrayList();
365 for (ifc : type.implements) {
366 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
373 private def Set<Type> getAllIfcs(Type type) {
374 val Set<Type> baseIfcs = new HashSet()
375 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
376 val ifc = type as GeneratedType
377 for (impl : ifc.implements) {
378 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
381 baseIfcs.addAll(impl.getAllIfcs)
387 private def List<String> toListOfNames(Collection<Type> types) {
388 val List<String> names = new ArrayList
390 names.add(type.fullyQualifiedName)
396 * Template method which generates class attributes.
398 * @param boolean value which specify whether field is|isn't final
399 * @return string with class attributes and their types
401 def private generateFields(boolean _final) '''
402 «IF properties !== null»
404 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
405 «val restrictions = f.returnType.restrictions»
406 «IF !_final && restrictions != null»
407 «IF !(restrictions.lengthConstraints.empty)»
408 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
409 private static «List.importedName»<«Range.importedName»<«clazz.importedNumber»>> «f.fieldName»_length;
411 «IF !(restrictions.rangeConstraints.empty)»
412 «val clazz = restrictions.rangeConstraints.iterator.next.min.class»
413 private static «List.importedName»<«Range.importedName»<«clazz.importedNumber»>> «f.fieldName»_range;
420 def private generateAugmentField(boolean init) '''
421 «IF augmentField != null»
422 private «Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();
427 * Template method which generates setter methods
429 * @return string with the setter methods
431 def private generateSetters() '''
432 «FOR field : properties SEPARATOR '\n'»
433 «val length = field.fieldName + "_length"»
434 «val range = field.fieldName + "_range"»
435 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
436 «generateRestrictions(field, "value", length, range)»
437 this.«field.fieldName» = value;
440 «generateLengthMethod(length, field.returnType, type.name+BUILDER, length)»
441 «generateRangeMethod(range, field.returnType, type.name+BUILDER, range)»
443 «IF augmentField != null»
445 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
446 this.«augmentField.name».put(augmentationType, augmentation);
452 def generateRestrictions(GeneratedProperty field, String paramName, String lengthGetter, String rangeGetter) '''
453 «val Type type = field.returnType»
454 «IF type instanceof ConcreteType»
455 «createRestrictions(type, paramName, type.name.contains("["), lengthGetter, rangeGetter)»
456 «ELSEIF type instanceof GeneratedTransferObject»
457 «createRestrictions(type, paramName, isArrayType(type as GeneratedTransferObject), lengthGetter, rangeGetter)»
461 def private createRestrictions(Type type, String paramName, boolean isArray, String lengthGetter, String rangeGetter) '''
462 «val restrictions = type.getRestrictions»
463 «IF restrictions !== null»
464 «val boolean isNestedType = !(type instanceof ConcreteType)»
465 «IF !restrictions.lengthConstraints.empty»
466 «generateLengthRestriction(type, paramName, lengthGetter, isNestedType, isArray)»
468 «IF !restrictions.rangeConstraints.empty»
469 «generateRangeRestriction(type, paramName, rangeGetter, isNestedType, isArray)»
474 def private generateLengthRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
475 «val restrictions = type.getRestrictions»
476 if («paramName» != null) {
477 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
478 «printLengthConstraint(type, clazz, paramName, isNestedType, isArray)»
479 boolean isValidLength = false;
480 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
481 if (r.contains(_constraint)) {
482 isValidLength = true;
485 if (!isValidLength) {
486 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «getterName»));
491 def private generateRangeRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
492 «val restrictions = type.getRestrictions»
493 if («paramName» != null) {
494 «val clazz = restrictions.rangeConstraints.iterator.next.min.class»
495 «printRangeConstraint(type, clazz, paramName, isNestedType)»
496 boolean isValidRange = false;
497 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
498 if (r.contains(_constraint)) {
503 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
509 * Template method which generate constructor for IMPL class.
511 * @return string with IMPL class constructor
513 def private generateConstructor() '''
514 private «type.name»«IMPL»(«type.name»«BUILDER» builder) {
515 «val allProps = new ArrayList(properties)»
516 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
517 «val keyType = type.getKey»
518 «IF isList && keyType != null»
519 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
520 «Collections.sort(keyProps,
522 return p1.name.compareTo(p2.name)
525 «FOR field : keyProps»
526 «removeProperty(allProps, field.name)»
528 «removeProperty(allProps, "key")»
529 if (builder.getKey() == null) {
530 this._key = new «keyType.importedName»(
531 «FOR keyProp : keyProps SEPARATOR ", "»
532 builder.«keyProp.getterMethodName»()
535 «FOR field : keyProps»
536 this.«field.fieldName» = builder.«field.getterMethodName»();
539 this._key = builder.getKey();
540 «FOR field : keyProps»
541 this.«field.fieldName» = _key.«field.getterMethodName»();
545 «FOR field : allProps»
546 this.«field.fieldName» = builder.«field.getterMethodName»();
548 «IF augmentField != null»
549 switch (builder.«augmentField.name».size()) {
551 this.«augmentField.name» = «Collections.importedName».emptyMap();
554 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = builder.«augmentField.name».entrySet().iterator().next();
555 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
558 this.«augmentField.name» = new «HashMap.importedName»<>(builder.«augmentField.name»);
564 private def boolean implementsIfc(GeneratedType type, Type impl) {
565 for (Type ifc : type.implements) {
566 if (ifc.equals(impl)) {
573 private def Type getKey(GeneratedType type) {
574 for (m : type.methodDefinitions) {
575 if ("getKey".equals(m.name)) {
582 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
583 var GeneratedProperty toRemove = null
585 if (p.name.equals(name)) {
589 if (toRemove != null) {
590 props.remove(toRemove);
595 * Template method which generate getter methods for IMPL class.
597 * @return string with getter methods
599 def private generateGetters(boolean addOverride) '''
600 «IF !properties.empty»
601 «FOR field : properties SEPARATOR '\n'»
602 «IF addOverride»@Override«ENDIF»
606 «IF augmentField != null»
608 @SuppressWarnings("unchecked")
609 «IF addOverride»@Override«ENDIF»
610 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
611 if (augmentationType == null) {
612 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
614 return (E) «augmentField.name».get(augmentationType);
620 * Template method which generates the method <code>hashCode()</code>.
622 * @return string with the <code>hashCode()</code> method definition in JAVA format
624 def protected generateHashCode() '''
625 «IF !properties.empty || augmentField != null»
627 public int hashCode() {
628 final int prime = 31;
630 «FOR property : properties»
631 «IF property.returnType.name.contains("[")»
632 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
634 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
637 «IF augmentField != null»
638 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
646 * Template method which generates the method <code>equals()</code>.
648 * @return string with the <code>equals()</code> method definition in JAVA format
650 def protected generateEquals() '''
651 «IF !properties.empty || augmentField != null»
653 public boolean equals(«Object.importedName» obj) {
660 if (getClass() != obj.getClass()) {
663 «type.name»«IMPL» other = («type.name»«IMPL») obj;
664 «FOR property : properties»
665 «val fieldName = property.fieldName»
666 if («fieldName» == null) {
667 if (other.«fieldName» != null) {
670 «IF property.returnType.name.contains("[")»
671 } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
673 } else if(!«fieldName».equals(other.«fieldName»)) {
678 «IF augmentField != null»
679 «val fieldName = augmentField.name»
680 if («fieldName» == null) {
681 if (other.«fieldName» != null) {
684 } else if(!«fieldName».equals(other.«fieldName»)) {
693 def override generateToString(Collection<GeneratedProperty> properties) '''
694 «IF !(properties === null)»
696 public «String.importedName» toString() {
697 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
698 boolean first = true;
700 «FOR property : properties»
701 if («property.fieldName» != null) {
705 builder.append(", ");
707 builder.append("«property.fieldName»=");
708 «IF property.returnType.name.contains("[")»
709 builder.append(«Arrays.importedName».toString(«property.fieldName»));
711 builder.append(«property.fieldName»);
715 «IF augmentField != null»
719 builder.append(", ");
721 builder.append("«augmentField.name»=");
722 builder.append(«augmentField.name».values());
724 return builder.append(']').toString();
729 override protected getFullyQualifiedName() {
730 '''«type.fullyQualifiedName»Builder'''.toString
733 def implementedInterfaceGetter() '''
734 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
735 return «type.importedName».class;