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 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_length;
410 «IF !(restrictions.rangeConstraints.empty)»
411 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_range;
418 def private generateAugmentField(boolean init) '''
419 «IF augmentField != null»
420 private «Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = new «HashMap.importedName»<>();
425 * Template method which generates setter methods
427 * @return string with the setter methods
429 def private generateSetters() '''
430 «FOR field : properties SEPARATOR '\n'»
431 «val length = field.fieldName + "_length"»
432 «val range = field.fieldName + "_range"»
433 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
434 «generateRestrictions(field, "value", length, range)»
435 this.«field.fieldName» = value;
438 «generateLengthMethod(length, field.returnType, type.name+BUILDER, length)»
439 «generateRangeMethod(range, field.returnType.restrictions, field.returnType, type.name+BUILDER, range)»
441 «IF augmentField != null»
443 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
444 this.«augmentField.name».put(augmentationType, augmentation);
450 def generateRestrictions(GeneratedProperty field, String paramName, String lengthGetter, String rangeGetter) '''
451 «val Type type = field.returnType»
452 «IF type instanceof ConcreteType»
453 «createRestrictions(type, paramName, type.name.contains("["), lengthGetter, rangeGetter)»
454 «ELSEIF type instanceof GeneratedTransferObject»
455 «createRestrictions(type, paramName, isArrayType(type as GeneratedTransferObject), lengthGetter, rangeGetter)»
459 def private createRestrictions(Type type, String paramName, boolean isArray, String lengthGetter, String rangeGetter) '''
460 «val restrictions = type.getRestrictions»
461 «IF restrictions !== null»
462 «val boolean isNestedType = !(type instanceof ConcreteType)»
463 «IF !restrictions.lengthConstraints.empty»
464 «generateLengthRestriction(type, paramName, lengthGetter, isNestedType, isArray)»
466 «IF !restrictions.rangeConstraints.empty»
467 «generateRangeRestriction(type, paramName, rangeGetter, isNestedType)»
472 def private generateLengthRestriction(Type type, String paramName, String getterName, boolean isNestedType, boolean isArray) '''
473 «val restrictions = type.getRestrictions»
474 if («paramName» != null) {
475 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
476 «printLengthConstraint(type, clazz, paramName, isNestedType, isArray)»
477 boolean isValidLength = false;
478 for («Range.importedName»<«clazz.importedNumber»> r : «getterName»()) {
479 if (r.contains(_constraint)) {
480 isValidLength = true;
483 if (!isValidLength) {
484 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «getterName»));
489 def private generateRangeRestriction(Type type, String paramName, String getterName, boolean isNestedType) '''
490 if («paramName» != null) {
491 «printRangeConstraint(type, paramName, isNestedType)»
492 boolean isValidRange = false;
493 for («Range.importedName»<«type.importedNumber»> r : «getterName»()) {
494 if (r.contains(_constraint)) {
499 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
505 * Template method which generate constructor for IMPL class.
507 * @return string with IMPL class constructor
509 def private generateConstructor() '''
510 private «type.name»«IMPL»(«type.name»«BUILDER» builder) {
511 «val allProps = new ArrayList(properties)»
512 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
513 «val keyType = type.getKey»
514 «IF isList && keyType != null»
515 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
516 «Collections.sort(keyProps,
518 return p1.name.compareTo(p2.name)
521 «FOR field : keyProps»
522 «removeProperty(allProps, field.name)»
524 «removeProperty(allProps, "key")»
525 if (builder.getKey() == null) {
526 this._key = new «keyType.importedName»(
527 «FOR keyProp : keyProps SEPARATOR ", "»
528 builder.«keyProp.getterMethodName»()
531 «FOR field : keyProps»
532 this.«field.fieldName» = builder.«field.getterMethodName»();
535 this._key = builder.getKey();
536 «FOR field : keyProps»
537 this.«field.fieldName» = _key.«field.getterMethodName»();
541 «FOR field : allProps»
542 this.«field.fieldName» = builder.«field.getterMethodName»();
544 «IF augmentField != null»
545 switch (builder.«augmentField.name».size()) {
547 this.«augmentField.name» = «Collections.importedName».emptyMap();
550 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = builder.«augmentField.name».entrySet().iterator().next();
551 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
554 this.«augmentField.name» = new «HashMap.importedName»<>(builder.«augmentField.name»);
560 private def boolean implementsIfc(GeneratedType type, Type impl) {
561 for (Type ifc : type.implements) {
562 if (ifc.equals(impl)) {
569 private def Type getKey(GeneratedType type) {
570 for (m : type.methodDefinitions) {
571 if ("getKey".equals(m.name)) {
578 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
579 var GeneratedProperty toRemove = null
581 if (p.name.equals(name)) {
585 if (toRemove != null) {
586 props.remove(toRemove);
591 * Template method which generate getter methods for IMPL class.
593 * @return string with getter methods
595 def private generateGetters(boolean addOverride) '''
596 «IF !properties.empty»
597 «FOR field : properties SEPARATOR '\n'»
598 «IF addOverride»@Override«ENDIF»
602 «IF augmentField != null»
604 @SuppressWarnings("unchecked")
605 «IF addOverride»@Override«ENDIF»
606 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
607 if (augmentationType == null) {
608 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
610 return (E) «augmentField.name».get(augmentationType);
616 * Template method which generates the method <code>hashCode()</code>.
618 * @return string with the <code>hashCode()</code> method definition in JAVA format
620 def protected generateHashCode() '''
621 «IF !properties.empty || augmentField != null»
623 public int hashCode() {
624 final int prime = 31;
626 «FOR property : properties»
627 «IF property.returnType.name.contains("[")»
628 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
630 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
633 «IF augmentField != null»
634 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
642 * Template method which generates the method <code>equals()</code>.
644 * @return string with the <code>equals()</code> method definition in JAVA format
646 def protected generateEquals() '''
647 «IF !properties.empty || augmentField != null»
649 public boolean equals(«Object.importedName» obj) {
656 if (getClass() != obj.getClass()) {
659 «type.name»«IMPL» other = («type.name»«IMPL») obj;
660 «FOR property : properties»
661 «val fieldName = property.fieldName»
662 if («fieldName» == null) {
663 if (other.«fieldName» != null) {
666 «IF property.returnType.name.contains("[")»
667 } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
669 } else if(!«fieldName».equals(other.«fieldName»)) {
674 «IF augmentField != null»
675 «val fieldName = augmentField.name»
676 if («fieldName» == null) {
677 if (other.«fieldName» != null) {
680 } else if(!«fieldName».equals(other.«fieldName»)) {
689 def override generateToString(Collection<GeneratedProperty> properties) '''
690 «IF !(properties === null)»
692 public «String.importedName» toString() {
693 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
694 boolean first = true;
696 «FOR property : properties»
697 if («property.fieldName» != null) {
701 builder.append(", ");
703 builder.append("«property.fieldName»=");
704 «IF property.returnType.name.contains("[")»
705 builder.append(«Arrays.importedName».toString(«property.fieldName»));
707 builder.append(«property.fieldName»);
711 «IF augmentField != null»
715 builder.append(", ");
717 builder.append("«augmentField.name»=");
718 builder.append(«augmentField.name».values());
720 return builder.append(']').toString();
725 override protected getFullyQualifiedName() {
726 '''«type.fullyQualifiedName»Builder'''.toString
729 def implementedInterfaceGetter() '''
730 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
731 return «type.importedName».class;