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.mdsal.binding.java.api.generator
10 import com.google.common.collect.ImmutableMap
11 import com.google.common.collect.ImmutableSortedSet
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
21 import java.util.Objects
23 import org.opendaylight.mdsal.binding.model.api.ConcreteType
24 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
25 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
26 import org.opendaylight.mdsal.binding.model.api.GeneratedType
27 import org.opendaylight.mdsal.binding.model.api.MethodSignature
28 import org.opendaylight.mdsal.binding.model.api.Type
29 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
30 import org.opendaylight.mdsal.binding.model.util.Types
31 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
32 import org.opendaylight.yangtools.concepts.Builder
33 import org.opendaylight.yangtools.yang.binding.Augmentable
34 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
35 import org.opendaylight.yangtools.yang.binding.DataObject
36 import org.opendaylight.yangtools.yang.binding.Identifiable
39 * Template for generating JAVA builder classes.
42 class BuilderTemplate extends BaseTemplate {
45 * Constant with the name of the concrete method.
47 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
50 * Constant with the suffix for builder classes.
52 val static BUILDER = 'Builder'
55 * Constant with the name of the BuilderFor interface
57 val static BUILDERFOR = Builder.simpleName;
60 * Constant with suffix for the classes which are generated from the builder classes.
62 val static IMPL = 'Impl'
65 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
67 var GeneratedProperty augmentField
70 * Set of class attributes (fields) which are derived from the getter methods names
72 val Set<GeneratedProperty> properties
74 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
77 * Constructs new instance of this class.
78 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
80 new(GeneratedType genType) {
82 this.properties = propertiesFromMethods(createMethods)
83 importMap.put(Builder.simpleName, Builder.package.name)
87 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
88 * and all the methods of the implemented interfaces.
90 * @returns set of method signature instances
92 def private Set<MethodSignature> createMethods() {
93 val Set<MethodSignature> methods = new LinkedHashSet();
94 methods.addAll(type.methodDefinitions)
95 collectImplementedMethods(methods, type.implements)
96 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
102 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
103 * and recursively their implemented interfaces.
105 * @param methods set of method signatures
106 * @param implementedIfcs list of implemented interfaces
108 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
109 if (implementedIfcs === null || implementedIfcs.empty) {
112 for (implementedIfc : implementedIfcs) {
113 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
114 val ifc = implementedIfc as GeneratedType
115 methods.addAll(ifc.methodDefinitions)
116 collectImplementedMethods(methods, ifc.implements)
117 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
118 for (m : Augmentable.methods) {
119 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
120 val fullyQualifiedName = m.returnType.name
121 val pkg = fullyQualifiedName.package
122 val name = fullyQualifiedName.name
123 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
124 val refType = new ReferencedTypeImpl(pkg, name)
125 val generic = new ReferencedTypeImpl(type.packageName, type.name)
126 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
127 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
128 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
136 * Returns the first element of the list <code>elements</code>.
138 * @param list of elements
140 def private <E> first(List<E> elements) {
145 * Returns the name of the package from <code>fullyQualifiedName</code>.
147 * @param fullyQualifiedName string with fully qualified type name (package + type)
148 * @return string with the package name
150 def private String getPackage(String fullyQualifiedName) {
151 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
152 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
156 * Returns the name of tye type from <code>fullyQualifiedName</code>
158 * @param fullyQualifiedName string with fully qualified type name (package + type)
159 * @return string with the name of the type
161 def private String getName(String fullyQualifiedName) {
162 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
163 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
167 * Creates set of generated property instances from getter <code>methods</code>.
169 * @param set of method signature instances which should be transformed to list of properties
170 * @return set of generated property instances which represents the getter <code>methods</code>
172 def private propertiesFromMethods(Collection<MethodSignature> methods) {
173 if (methods === null || methods.isEmpty()) {
174 return Collections.emptySet
176 val Set<GeneratedProperty> result = new LinkedHashSet
178 val createdField = m.propertyFromGetter
179 if (createdField !== null) {
180 result.add(createdField)
187 * Creates generated property instance from the getter <code>method</code> name and return type.
189 * @param method method signature from which is the method name and return type obtained
190 * @return generated property instance for the getter <code>method</code>
191 * @throws IllegalArgumentException<ul>
192 * <li>if the <code>method</code> equals <code>null</code></li>
193 * <li>if the name of the <code>method</code> equals <code>null</code></li>
194 * <li>if the name of the <code>method</code> is empty</li>
195 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
198 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
199 if (method === null || method.name === null || method.name.empty || method.returnType === null) {
200 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
203 if (Types.BOOLEAN.equals(method.returnType)) {
206 if (method.name.startsWith(prefix)) {
207 val fieldName = method.getName().substring(prefix.length()).toFirstLower
208 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
209 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
210 return tmpGenTO.toInstance.properties.first
214 override isLocalInnerClass(String importedTypePackageName) {
215 // Builders do not have inner types
220 * Template method which generates JAVA class body for builder class and for IMPL class.
222 * @return string with JAVA source code
225 «wrapToDocumentation(formatDataForJavaDoc(type))»
226 public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
228 «generateFields(false)»
230 «generateAugmentField(false)»
232 «generateConstructorsFromIfcs(type)»
234 «generateCopyConstructor(false)»
236 «generateMethodFieldsFrom(type)»
238 «generateGetters(false)»
243 public «type.name» build() {
244 return new «type.name»«IMPL»(this);
247 private static final class «type.name»«IMPL» implements «type.name» {
249 «implementedInterfaceGetter»
251 «generateFields(true)»
253 «generateAugmentField(true)»
255 «generateCopyConstructor(true)»
257 «generateGetters(true)»
263 «generateToString(properties)»
270 * Generate default constructor and constructor for every implemented interface from uses statements.
272 def private generateConstructorsFromIfcs(Type type) '''
273 public «type.name»«BUILDER»() {
275 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
276 «val ifc = type as GeneratedType»
277 «FOR impl : ifc.implements»
278 «generateConstructorFromIfc(impl)»
284 * Generate constructor with argument of given type.
286 def private Object generateConstructorFromIfc(Type impl) '''
287 «IF (impl instanceof GeneratedType)»
288 «IF !(impl.methodDefinitions.empty)»
289 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
290 «printConstructorPropertySetter(impl)»
293 «FOR implTypeImplement : impl.implements»
294 «generateConstructorFromIfc(implTypeImplement)»
299 def private Object printConstructorPropertySetter(Type implementedIfc) '''
300 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
301 «val ifc = implementedIfc as GeneratedType»
302 «FOR getter : ifc.methodDefinitions»
303 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
305 «FOR impl : ifc.implements»
306 «printConstructorPropertySetter(impl)»
312 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
314 def private generateMethodFieldsFrom(Type type) '''
315 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
316 «val ifc = type as GeneratedType»
317 «IF ifc.hasImplementsFromUses»
318 «val List<Type> done = ifc.getBaseIfcs»
319 «generateMethodFieldsFromComment(ifc)»
320 public void fieldsFrom(«DataObject.importedName» arg) {
321 boolean isValidArg = false;
322 «FOR impl : ifc.getAllIfcs»
323 «generateIfCheck(impl, done)»
326 throw new IllegalArgumentException(
327 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
336 def private generateMethodFieldsFromComment(GeneratedType type) '''
338 *Set fields from given grouping argument. Valid argument is instance of one of following types:
340 «FOR impl : type.getAllIfcs»
341 * <li>«impl.fullyQualifiedName»</li>
345 * @param arg grouping object
346 * @throws IllegalArgumentException if given argument is none of valid types
351 * Method is used to find out if given type implements any interface from uses.
353 def boolean hasImplementsFromUses(GeneratedType type) {
355 for (impl : type.getAllIfcs) {
356 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
363 def private generateIfCheck(Type impl, List<Type> done) '''
364 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
365 «val implType = impl as GeneratedType»
366 if (arg instanceof «implType.fullyQualifiedName») {
367 «printPropertySetter(implType)»
373 def private printPropertySetter(Type implementedIfc) '''
374 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
375 «val ifc = implementedIfc as GeneratedType»
376 «FOR getter : ifc.methodDefinitions»
377 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
382 private def List<Type> getBaseIfcs(GeneratedType type) {
383 val List<Type> baseIfcs = new ArrayList();
384 for (ifc : type.implements) {
385 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
392 private def Set<Type> getAllIfcs(Type type) {
393 val Set<Type> baseIfcs = new HashSet()
394 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
395 val ifc = type as GeneratedType
396 for (impl : ifc.implements) {
397 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
400 baseIfcs.addAll(impl.getAllIfcs)
406 private def List<String> toListOfNames(Collection<Type> types) {
407 val List<String> names = new ArrayList
409 names.add(type.fullyQualifiedName)
415 * Template method which generates class attributes.
417 * @param boolean value which specify whether field is|isn't final
418 * @return string with class attributes and their types
420 def private generateFields(boolean _final) '''
421 «IF properties !== null»
423 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
428 def private generateAugmentField(boolean isPrivate) '''
429 «IF augmentField !== null»
430 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
435 * Template method which generates setter methods
437 * @return string with the setter methods
439 def private generateSetters() '''
440 «FOR field : properties SEPARATOR '\n'»
441 «/* FIXME: generate checkers as simple blocks and embed them directly in setters */»
442 «val restrictions = field.returnType.restrictions»
443 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
444 «IF restrictions.rangeConstraint.present»
445 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
446 «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get)»
449 «IF restrictions.lengthConstraint.present»
450 «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get)»
454 public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
455 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
456 «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
458 «IF restrictions.rangeConstraint.present»
459 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
460 «IF field.returnType instanceof ConcreteType»
461 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
463 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
466 «IF restrictions.lengthConstraint.present»
467 «IF field.returnType instanceof ConcreteType»
468 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
470 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
476 this.«field.fieldName» = value;
480 «IF augmentField !== null»
482 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
483 if (augmentationValue == null) {
484 return remove«augmentField.name.toFirstUpper»(augmentationType);
487 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
488 this.«augmentField.name» = new «HashMap.importedName»<>();
491 this.«augmentField.name».put(augmentationType, augmentationValue);
495 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
496 if (this.«augmentField.name» instanceof «HashMap.importedName») {
497 this.«augmentField.name».remove(augmentationType);
504 def private CharSequence generateCopyConstructor(boolean impl) '''
505 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
506 «val allProps = new ArrayList(properties)»
507 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
508 «val keyType = type.getKey»
509 «IF isList && keyType !== null»
510 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
511 «Collections.sort(keyProps,
513 return p1.name.compareTo(p2.name)
516 «FOR field : keyProps»
517 «removeProperty(allProps, field.name)»
519 «removeProperty(allProps, "key")»
520 if (base.getKey() == null) {
521 this._key = new «keyType.importedName»(
522 «FOR keyProp : keyProps SEPARATOR ", "»
523 base.«keyProp.getterMethodName»()
526 «FOR field : keyProps»
527 this.«field.fieldName» = base.«field.getterMethodName»();
530 this._key = base.getKey();
531 «FOR field : keyProps»
532 this.«field.fieldName» = _key.«field.getterMethodName»();
536 «FOR field : allProps»
537 this.«field.fieldName» = base.«field.getterMethodName»();
539 «IF augmentField !== null»
541 this.«augmentField.name» = «ImmutableMap.importedName».copyOf(base.«augmentField.name»);
543 if (base instanceof «type.name»«IMPL») {
544 «type.name»«IMPL» impl = («type.name»«IMPL») base;
545 if (!impl.«augmentField.name».isEmpty()) {
546 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
548 } else if (base instanceof «AugmentationHolder.importedName») {
549 @SuppressWarnings("unchecked")
550 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
551 if (!casted.augmentations().isEmpty()) {
552 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
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»
622 private int hash = 0;
623 private volatile boolean hashValid = false;
626 public int hashCode() {
631 final int prime = 31;
633 «FOR property : properties»
634 «IF property.returnType.name.contains("[")»
635 result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
637 result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
640 «IF augmentField !== null»
641 result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
652 * Template method which generates the method <code>equals()</code>.
654 * @return string with the <code>equals()</code> method definition in JAVA format
656 def protected generateEquals() '''
657 «IF !properties.empty || augmentField !== null»
659 public boolean equals(«Object.importedName» obj) {
663 if (!(obj instanceof «DataObject.importedName»)) {
666 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
669 «type.importedName» other = («type.importedName»)obj;
670 «FOR property : properties»
671 «val fieldName = property.fieldName»
672 «IF property.returnType.name.contains("[")»
673 if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
675 if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
680 «IF augmentField !== null»
681 if (getClass() == obj.getClass()) {
682 // Simple case: we are comparing against self
683 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
684 «val fieldName = augmentField.name»
685 if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
689 // Hard case: compare our augments with presence there...
690 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
691 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
695 // .. and give the other one the chance to do the same
696 if (!obj.equals(this)) {
706 def override generateToString(Collection<GeneratedProperty> properties) '''
707 «IF !(properties === null)»
709 public «String.importedName» toString() {
710 «String.importedName» name = "«type.name» [";
711 «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
712 «FOR property : properties SEPARATOR "\n builder.append(\", \");\n}" AFTER " }\n"»
713 if («property.fieldName» != null) {
714 builder.append("«property.fieldName»=");
715 «IF property.returnType.name.contains("[")»
716 builder.append(«Arrays.importedName».toString(«property.fieldName»));
718 builder.append(«property.fieldName»);
721 «IF augmentField !== null»
722 «IF !properties.empty»
723 «««Append comma separator only if it's not there already from previous operation»»»
724 final int builderLength = builder.length();
725 final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
726 if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
727 builder.append(", ");
730 builder.append("«augmentField.name»=");
731 builder.append(«augmentField.name».values());«"\n"»
732 return builder.append(']').toString();
734 «IF properties.empty»
735 return builder.append(']').toString();
737 return builder.append(']').toString();
744 def implementedInterfaceGetter() '''
746 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
747 return «type.importedName».class;
751 private def createDescription(GeneratedType type) {
753 Class that builds {@link «type.importedName»} instances.
755 @see «type.importedName»
759 override def protected String formatDataForJavaDoc(GeneratedType type) {
760 val typeDescription = createDescription(type)
763 «IF !typeDescription.nullOrEmpty»