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 java.util.ArrayList
12 import java.util.Arrays
13 import java.util.Collection
14 import java.util.Collections
15 import java.util.HashMap
16 import java.util.HashSet
17 import java.util.LinkedHashSet
21 import org.opendaylight.yangtools.binding.generator.util.ReferencedTypeImpl
22 import org.opendaylight.yangtools.binding.generator.util.Types
23 import org.opendaylight.yangtools.binding.generator.util.generated.type.builder.GeneratedTOBuilderImpl
24 import org.opendaylight.yangtools.concepts.Builder
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.AugmentationHolder
33 import org.opendaylight.yangtools.yang.binding.DataObject
34 import org.opendaylight.yangtools.yang.binding.Identifiable
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 && !(restrictions.lengthConstraints.empty)»
418 «LengthGenerator.generateLengthChecker(f.fieldName.toString, f.returnType, restrictions.lengthConstraints)»
424 def private generateAugmentField(boolean isPrivate) '''
425 «IF augmentField != null»
426 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
431 * Template method which generates setter methods
433 * @return string with the setter methods
435 def private generateSetters() '''
436 «FOR field : properties SEPARATOR '\n'»
437 «val restrictions = field.returnType.restrictions»
438 «IF restrictions != null»
439 «IF !restrictions.rangeConstraints.nullOrEmpty»
440 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
441 «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraints)»
445 public «type.name»«BUILDER» set«field.name.toFirstUpper»(«field.returnType.importedName» value) {
446 «IF restrictions != null»
448 «IF !restrictions.rangeConstraints.nullOrEmpty»
449 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
450 «IF field.returnType instanceof ConcreteType»
451 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
453 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
456 «generateRestrictions(field, "value")»
459 this.«field.fieldName» = value;
463 «IF augmentField != null»
465 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
466 if (augmentation == null) {
467 return remove«augmentField.name.toFirstUpper»(augmentationType);
470 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
471 this.«augmentField.name» = new «HashMap.importedName»<>();
474 this.«augmentField.name».put(augmentationType, augmentation);
478 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
479 if (this.«augmentField.name» instanceof «HashMap.importedName») {
480 this.«augmentField.name».remove(augmentationType);
487 def private generateRestrictions(GeneratedProperty field, String paramName) '''
488 «val Type type = field.returnType»
489 «val restrictions = type.getRestrictions»
490 «IF restrictions !== null && !restrictions.lengthConstraints.empty»
491 «IF type instanceof ConcreteType»
492 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, paramName)»
494 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, paramName + ".getValue()")»
499 def private CharSequence generateCopyConstructor(boolean impl) '''
500 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
501 «val allProps = new ArrayList(properties)»
502 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
503 «val keyType = type.getKey»
504 «IF isList && keyType != null»
505 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
506 «Collections.sort(keyProps,
508 return p1.name.compareTo(p2.name)
511 «FOR field : keyProps»
512 «removeProperty(allProps, field.name)»
514 «removeProperty(allProps, "key")»
515 if (base.getKey() == null) {
516 this._key = new «keyType.importedName»(
517 «FOR keyProp : keyProps SEPARATOR ", "»
518 base.«keyProp.getterMethodName»()
521 «FOR field : keyProps»
522 this.«field.fieldName» = base.«field.getterMethodName»();
525 this._key = base.getKey();
526 «FOR field : keyProps»
527 this.«field.fieldName» = _key.«field.getterMethodName»();
531 «FOR field : allProps»
532 this.«field.fieldName» = base.«field.getterMethodName»();
534 «IF augmentField != null»
536 switch (base.«augmentField.name».size()) {
538 this.«augmentField.name» = «Collections.importedName».emptyMap();
541 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
542 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
545 this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
548 if (base instanceof «type.name»«IMPL») {
549 «type.name»«IMPL» impl = («type.name»«IMPL») base;
550 if (!impl.«augmentField.name».isEmpty()) {
551 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
553 } else if (base instanceof «AugmentationHolder.importedName») {
554 @SuppressWarnings("unchecked")
555 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
556 if (!casted.augmentations().isEmpty()) {
557 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
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»
627 private int hash = 0;
628 private volatile boolean hashValid = false;
631 public int hashCode() {
636 final int prime = 31;
638 «FOR property : properties»
639 «IF property.returnType.name.contains("[")»
640 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
642 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
645 «IF augmentField != null»
646 result = prime * result + ((«augmentField.name» == null) ? 0 : «augmentField.name».hashCode());
657 * Template method which generates the method <code>equals()</code>.
659 * @return string with the <code>equals()</code> method definition in JAVA format
661 def protected generateEquals() '''
662 «IF !properties.empty || augmentField != null»
664 public boolean equals(«Object.importedName» obj) {
668 if (!(obj instanceof «DataObject.importedName»)) {
671 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
674 «type.importedName» other = («type.importedName»)obj;
675 «FOR property : properties»
676 «val fieldName = property.fieldName»
677 if («fieldName» == null) {
678 if (other.«property.getterMethodName»() != null) {
681 «IF property.returnType.name.contains("[")»
682 } else if(!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
684 } else if(!«fieldName».equals(other.«property.getterMethodName»())) {
689 «IF augmentField != null»
690 if (getClass() == obj.getClass()) {
691 // Simple case: we are comparing against self
692 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
693 «val fieldName = augmentField.name»
694 if («fieldName» == null) {
695 if (otherImpl.«fieldName» != null) {
698 } else if(!«fieldName».equals(otherImpl.«fieldName»)) {
702 // Hard case: compare our augments with presence there...
703 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
704 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
708 // .. and give the other one the chance to do the same
709 if (!obj.equals(this)) {
719 def override generateToString(Collection<GeneratedProperty> properties) '''
720 «IF !(properties === null)»
722 public «String.importedName» toString() {
723 «StringBuilder.importedName» builder = new «StringBuilder.importedName» ("«type.name» [");
724 boolean first = true;
726 «FOR property : properties»
727 if («property.fieldName» != null) {
731 builder.append(", ");
733 builder.append("«property.fieldName»=");
734 «IF property.returnType.name.contains("[")»
735 builder.append(«Arrays.importedName».toString(«property.fieldName»));
737 builder.append(«property.fieldName»);
741 «IF augmentField != null»
745 builder.append(", ");
747 builder.append("«augmentField.name»=");
748 builder.append(«augmentField.name».values());
750 return builder.append(']').toString();
755 def implementedInterfaceGetter() '''
756 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
757 return «type.importedName».class;
761 private def createDescription(GeneratedType type) {
763 Class that builds {@link «type.importedName»} instances.
765 @see «type.importedName»
769 override def protected String formatDataForJavaDoc(GeneratedType type) {
770 val typeDescription = createDescription(type)
773 «IF !typeDescription.nullOrEmpty»