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
207 «wrapToDocumentation(formatDataForJavaDoc(type))»
208 public class «type.name»«BUILDER» {
210 «generateFields(false)»
212 «generateAugmentField(true)»
214 «generateConstructorsFromIfcs(type)»
216 «generateCopyConstructor(false)»
218 «generateMethodFieldsFrom(type)»
220 «generateGetters(false)»
224 public «type.name» build() {
225 return new «type.name»«IMPL»(this);
228 private static final class «type.name»«IMPL» implements «type.name» {
230 «implementedInterfaceGetter»
232 «generateFields(true)»
234 «generateAugmentField(false)»
236 «generateCopyConstructor(true)»
238 «generateGetters(true)»
244 «generateToString(properties)»
251 * Generate default constructor and constructor for every implemented interface from uses statements.
253 def private generateConstructorsFromIfcs(Type type) '''
254 public «type.name»«BUILDER»() {
256 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
257 «val ifc = type as GeneratedType»
258 «FOR impl : ifc.implements»
259 «generateConstructorFromIfc(impl)»
265 * Generate constructor with argument of given type.
267 def private Object generateConstructorFromIfc(Type impl) '''
268 «IF (impl instanceof GeneratedType)»
269 «val implType = impl as GeneratedType»
271 «IF !(implType.methodDefinitions.empty)»
272 public «type.name»«BUILDER»(«implType.fullyQualifiedName» arg) {
273 «printConstructorPropertySetter(implType)»
276 «FOR implTypeImplement : implType.implements»
277 «generateConstructorFromIfc(implTypeImplement)»
282 def private Object printConstructorPropertySetter(Type implementedIfc) '''
283 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
284 «val ifc = implementedIfc as GeneratedType»
285 «FOR getter : ifc.methodDefinitions»
286 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
288 «FOR impl : ifc.implements»
289 «printConstructorPropertySetter(impl)»
295 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
297 def private generateMethodFieldsFrom(Type type) '''
298 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
299 «val ifc = type as GeneratedType»
300 «IF ifc.hasImplementsFromUses»
301 «val List<Type> done = ifc.getBaseIfcs»
302 «generateMethodFieldsFromComment(ifc)»
303 public void fieldsFrom(«DataObject.importedName» arg) {
304 boolean isValidArg = false;
305 «FOR impl : ifc.getAllIfcs»
306 «generateIfCheck(impl, done)»
309 throw new IllegalArgumentException(
310 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
319 def private generateMethodFieldsFromComment(GeneratedType type) '''
321 *Set fields from given grouping argument. Valid argument is instance of one of following types:
323 «FOR impl : type.getAllIfcs»
324 * <li>«impl.fullyQualifiedName»</li>
328 * @param arg grouping object
329 * @throws IllegalArgumentException if given argument is none of valid types
334 * Method is used to find out if given type implements any interface from uses.
336 def boolean hasImplementsFromUses(GeneratedType type) {
338 for (impl : type.getAllIfcs) {
339 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
346 def private generateIfCheck(Type impl, List<Type> done) '''
347 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
348 «val implType = impl as GeneratedType»
349 if (arg instanceof «implType.fullyQualifiedName») {
350 «printPropertySetter(implType)»
356 def private printPropertySetter(Type implementedIfc) '''
357 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
358 «val ifc = implementedIfc as GeneratedType»
359 «FOR getter : ifc.methodDefinitions»
360 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
365 private def List<Type> getBaseIfcs(GeneratedType type) {
366 val List<Type> baseIfcs = new ArrayList();
367 for (ifc : type.implements) {
368 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
375 private def Set<Type> getAllIfcs(Type type) {
376 val Set<Type> baseIfcs = new HashSet()
377 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
378 val ifc = type as GeneratedType
379 for (impl : ifc.implements) {
380 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
383 baseIfcs.addAll(impl.getAllIfcs)
389 private def List<String> toListOfNames(Collection<Type> types) {
390 val List<String> names = new ArrayList
392 names.add(type.fullyQualifiedName)
398 * Template method which generates class attributes.
400 * @param boolean value which specify whether field is|isn't final
401 * @return string with class attributes and their types
403 def private generateFields(boolean _final) '''
404 «IF properties !== null»
406 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
407 «val restrictions = f.returnType.restrictions»
408 «IF !_final && restrictions != null»
409 «IF !(restrictions.lengthConstraints.empty)»
410 private static «List.importedName»<«Range.importedName»<«f.returnType.importedNumber»>> «f.fieldName»_length;
412 «IF !(restrictions.rangeConstraints.empty)»
413 private static «List.importedName»<«Range.importedName»<«f.returnType.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.restrictions, 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)»
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) '''
492 if («paramName» != null) {
493 «printRangeConstraint(type, paramName, isNestedType)»
494 boolean isValidRange = false;
495 for («Range.importedName»<«type.importedNumber»> r : «getterName»()) {
496 if (r.contains(_constraint)) {
501 throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «getterName»));
506 def private CharSequence generateCopyConstructor(boolean impl) '''
507 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
508 «val allProps = new ArrayList(properties)»
509 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
510 «val keyType = type.getKey»
511 «IF isList && keyType != null»
512 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
513 «Collections.sort(keyProps,
515 return p1.name.compareTo(p2.name)
518 «FOR field : keyProps»
519 «removeProperty(allProps, field.name)»
521 «removeProperty(allProps, "key")»
522 if (base.getKey() == null) {
523 this._key = new «keyType.importedName»(
524 «FOR keyProp : keyProps SEPARATOR ", "»
525 base.«keyProp.getterMethodName»()
528 «FOR field : keyProps»
529 this.«field.fieldName» = base.«field.getterMethodName»();
532 this._key = base.getKey();
533 «FOR field : keyProps»
534 this.«field.fieldName» = _key.«field.getterMethodName»();
538 «FOR field : allProps»
539 this.«field.fieldName» = base.«field.getterMethodName»();
541 «IF augmentField != null»
542 «IF !impl»if (base instanceof «type.name»«IMPL») {«ENDIF»
543 «IF !impl»«type.name»«IMPL» _impl = («type.name»«IMPL») base;«ENDIF»
544 «val prop = if (impl) "base" else "_impl"»
546 switch («prop».«augmentField.name».size()) {
548 this.«augmentField.name» = «Collections.importedName».emptyMap();
551 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = «prop».«augmentField.name».entrySet().iterator().next();
552 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
555 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
558 this.«augmentField.name» = new «HashMap.importedName»<>(«prop».«augmentField.name»);
565 private def boolean implementsIfc(GeneratedType type, Type impl) {
566 for (Type ifc : type.implements) {
567 if (ifc.equals(impl)) {
574 private def Type getKey(GeneratedType type) {
575 for (m : type.methodDefinitions) {
576 if ("getKey".equals(m.name)) {
583 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
584 var GeneratedProperty toRemove = null
586 if (p.name.equals(name)) {
590 if (toRemove != null) {
591 props.remove(toRemove);
596 * Template method which generate getter methods for IMPL class.
598 * @return string with getter methods
600 def private generateGetters(boolean addOverride) '''
601 «IF !properties.empty»
602 «FOR field : properties SEPARATOR '\n'»
603 «IF addOverride»@Override«ENDIF»
607 «IF augmentField != null»
609 @SuppressWarnings("unchecked")
610 «IF addOverride»@Override«ENDIF»
611 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
612 if (augmentationType == null) {
613 throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!");
615 return (E) «augmentField.name».get(augmentationType);
621 * Template method which generates the method <code>hashCode()</code>.
623 * @return string with the <code>hashCode()</code> method definition in JAVA format
625 def protected generateHashCode() '''
626 «IF !properties.empty || augmentField != null»
628 public int hashCode() {
629 final int prime = 31;
631 «FOR property : properties»
632 «IF property.returnType.name.contains("[")»
633 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
635 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
638 «IF augmentField != null»
639 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
647 * Template method which generates the method <code>equals()</code>.
649 * @return string with the <code>equals()</code> method definition in JAVA format
651 def protected generateEquals() '''
652 «IF !properties.empty || augmentField != null»
654 public boolean equals(«Object.importedName» obj) {
661 if (getClass() != obj.getClass()) {
664 «type.name»«IMPL» other = («type.name»«IMPL») obj;
665 «FOR property : properties»
666 «val fieldName = property.fieldName»
667 if («fieldName» == null) {
668 if (other.«fieldName» != null) {
671 «IF property.returnType.name.contains("[")»
672 } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
674 } else if(!«fieldName».equals(other.«fieldName»)) {
679 «IF augmentField != null»
680 «val fieldName = augmentField.name»
681 if («fieldName» == null) {
682 if (other.«fieldName» != null) {
685 } else if(!«fieldName».equals(other.«fieldName»)) {
694 def override generateToString(Collection<GeneratedProperty> properties) '''
695 «IF !(properties === null)»
697 public «String.importedName» toString() {
698 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
699 boolean first = true;
701 «FOR property : properties»
702 if («property.fieldName» != null) {
706 builder.append(", ");
708 builder.append("«property.fieldName»=");
709 «IF property.returnType.name.contains("[")»
710 builder.append(«Arrays.importedName».toString(«property.fieldName»));
712 builder.append(«property.fieldName»);
716 «IF augmentField != null»
720 builder.append(", ");
722 builder.append("«augmentField.name»=");
723 builder.append(«augmentField.name».values());
725 return builder.append(']').toString();
730 override protected getFullyQualifiedName() {
731 '''«type.fullyQualifiedName»Builder'''.toString
734 def implementedInterfaceGetter() '''
735 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
736 return «type.importedName».class;