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.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
20 import java.util.Objects
22 import org.opendaylight.mdsal.binding.model.api.ConcreteType
23 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
24 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
25 import org.opendaylight.mdsal.binding.model.api.GeneratedType
26 import org.opendaylight.mdsal.binding.model.api.MethodSignature
27 import org.opendaylight.mdsal.binding.model.api.Type
28 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
29 import org.opendaylight.mdsal.binding.model.util.Types
30 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.GeneratedTOBuilderImpl
31 import org.opendaylight.yangtools.concepts.Builder
32 import org.opendaylight.yangtools.yang.binding.Augmentable
33 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
34 import org.opendaylight.yangtools.yang.binding.DataObject
35 import org.opendaylight.yangtools.yang.binding.Identifiable
38 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends BaseTemplate {
44 * Constant with the name of the concrete method.
46 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
49 * Constant with the suffix for builder classes.
51 val static BUILDER = 'Builder'
54 * Constant with the name of the BuilderFor interface
56 val static BUILDERFOR = Builder.simpleName;
59 * Constant with suffix for the classes which are generated from the builder classes.
61 val static IMPL = 'Impl'
64 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
66 var GeneratedProperty augmentField
69 * Set of class attributes (fields) which are derived from the getter methods names
71 val Set<GeneratedProperty> properties
73 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
76 * Constructs new instance of this class.
77 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
79 new(GeneratedType genType) {
81 this.properties = propertiesFromMethods(createMethods)
82 importMap.put(Builder.simpleName, Builder.package.name)
86 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
87 * and all the methods of the implemented interfaces.
89 * @returns set of method signature instances
91 def private Set<MethodSignature> createMethods() {
92 val Set<MethodSignature> methods = new LinkedHashSet();
93 methods.addAll(type.methodDefinitions)
94 collectImplementedMethods(methods, type.implements)
95 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
101 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
102 * and recursively their implemented interfaces.
104 * @param methods set of method signatures
105 * @param implementedIfcs list of implemented interfaces
107 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
108 if (implementedIfcs === null || implementedIfcs.empty) {
111 for (implementedIfc : implementedIfcs) {
112 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
113 val ifc = implementedIfc as GeneratedType
114 methods.addAll(ifc.methodDefinitions)
115 collectImplementedMethods(methods, ifc.implements)
116 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
117 for (m : Augmentable.methods) {
118 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
119 val fullyQualifiedName = m.returnType.name
120 val pkg = fullyQualifiedName.package
121 val name = fullyQualifiedName.name
122 val tmpGenTO = new GeneratedTOBuilderImpl(pkg, name)
123 val refType = new ReferencedTypeImpl(pkg, name)
124 val generic = new ReferencedTypeImpl(type.packageName, type.name)
125 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
126 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
127 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
135 * Returns the first element of the list <code>elements</code>.
137 * @param list of elements
139 def private <E> first(List<E> elements) {
144 * Returns the name of the package from <code>fullyQualifiedName</code>.
146 * @param fullyQualifiedName string with fully qualified type name (package + type)
147 * @return string with the package name
149 def private String getPackage(String fullyQualifiedName) {
150 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
151 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
155 * Returns the name of tye type from <code>fullyQualifiedName</code>
157 * @param fullyQualifiedName string with fully qualified type name (package + type)
158 * @return string with the name of the type
160 def private String getName(String fullyQualifiedName) {
161 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
162 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
166 * Creates set of generated property instances from getter <code>methods</code>.
168 * @param set of method signature instances which should be transformed to list of properties
169 * @return set of generated property instances which represents the getter <code>methods</code>
171 def private propertiesFromMethods(Collection<MethodSignature> methods) {
172 if (methods === null || methods.isEmpty()) {
173 return Collections.emptySet
175 val Set<GeneratedProperty> result = new LinkedHashSet
177 val createdField = m.propertyFromGetter
178 if (createdField !== null) {
179 result.add(createdField)
186 * Creates generated property instance from the getter <code>method</code> name and return type.
188 * @param method method signature from which is the method name and return type obtained
189 * @return generated property instance for the getter <code>method</code>
190 * @throws IllegalArgumentException<ul>
191 * <li>if the <code>method</code> equals <code>null</code></li>
192 * <li>if the name of the <code>method</code> equals <code>null</code></li>
193 * <li>if the name of the <code>method</code> is empty</li>
194 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
197 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
198 if (method === null || method.name === null || method.name.empty || method.returnType === null) {
199 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
202 if (Types.BOOLEAN.equals(method.returnType)) {
205 if (method.name.startsWith(prefix)) {
206 val fieldName = method.getName().substring(prefix.length()).toFirstLower
207 val tmpGenTO = new GeneratedTOBuilderImpl("foo", "foo")
208 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
209 return tmpGenTO.toInstance.properties.first
214 * Template method which generates JAVA class body for builder class and for IMPL class.
216 * @return string with JAVA source code
219 «wrapToDocumentation(formatDataForJavaDoc(type))»
220 public class «type.name»«BUILDER» implements «BUILDERFOR» <«type.importedName»> {
222 «generateFields(false)»
224 «generateAugmentField(false)»
226 «generateConstructorsFromIfcs(type)»
228 «generateCopyConstructor(false)»
230 «generateMethodFieldsFrom(type)»
232 «generateGetters(false)»
237 public «type.name» build() {
238 return new «type.name»«IMPL»(this);
241 private static final class «type.name»«IMPL» implements «type.name» {
243 «implementedInterfaceGetter»
245 «generateFields(true)»
247 «generateAugmentField(true)»
249 «generateCopyConstructor(true)»
251 «generateGetters(true)»
257 «generateToString(properties)»
264 * Generate default constructor and constructor for every implemented interface from uses statements.
266 def private generateConstructorsFromIfcs(Type type) '''
267 public «type.name»«BUILDER»() {
269 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
270 «val ifc = type as GeneratedType»
271 «FOR impl : ifc.implements»
272 «generateConstructorFromIfc(impl)»
278 * Generate constructor with argument of given type.
280 def private Object generateConstructorFromIfc(Type impl) '''
281 «IF (impl instanceof GeneratedType)»
282 «IF !(impl.methodDefinitions.empty)»
283 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
284 «printConstructorPropertySetter(impl)»
287 «FOR implTypeImplement : impl.implements»
288 «generateConstructorFromIfc(implTypeImplement)»
293 def private Object printConstructorPropertySetter(Type implementedIfc) '''
294 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
295 «val ifc = implementedIfc as GeneratedType»
296 «FOR getter : ifc.methodDefinitions»
297 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
299 «FOR impl : ifc.implements»
300 «printConstructorPropertySetter(impl)»
306 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
308 def private generateMethodFieldsFrom(Type type) '''
309 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
310 «val ifc = type as GeneratedType»
311 «IF ifc.hasImplementsFromUses»
312 «val List<Type> done = ifc.getBaseIfcs»
313 «generateMethodFieldsFromComment(ifc)»
314 public void fieldsFrom(«DataObject.importedName» arg) {
315 boolean isValidArg = false;
316 «FOR impl : ifc.getAllIfcs»
317 «generateIfCheck(impl, done)»
320 throw new IllegalArgumentException(
321 "expected one of: «ifc.getAllIfcs.toListOfNames» \n" +
330 def private generateMethodFieldsFromComment(GeneratedType type) '''
332 *Set fields from given grouping argument. Valid argument is instance of one of following types:
334 «FOR impl : type.getAllIfcs»
335 * <li>«impl.fullyQualifiedName»</li>
339 * @param arg grouping object
340 * @throws IllegalArgumentException if given argument is none of valid types
345 * Method is used to find out if given type implements any interface from uses.
347 def boolean hasImplementsFromUses(GeneratedType type) {
349 for (impl : type.getAllIfcs) {
350 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
357 def private generateIfCheck(Type impl, List<Type> done) '''
358 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
359 «val implType = impl as GeneratedType»
360 if (arg instanceof «implType.fullyQualifiedName») {
361 «printPropertySetter(implType)»
367 def private printPropertySetter(Type implementedIfc) '''
368 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
369 «val ifc = implementedIfc as GeneratedType»
370 «FOR getter : ifc.methodDefinitions»
371 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
376 private def List<Type> getBaseIfcs(GeneratedType type) {
377 val List<Type> baseIfcs = new ArrayList();
378 for (ifc : type.implements) {
379 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
386 private def Set<Type> getAllIfcs(Type type) {
387 val Set<Type> baseIfcs = new HashSet()
388 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
389 val ifc = type as GeneratedType
390 for (impl : ifc.implements) {
391 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
394 baseIfcs.addAll(impl.getAllIfcs)
400 private def List<String> toListOfNames(Collection<Type> types) {
401 val List<String> names = new ArrayList
403 names.add(type.fullyQualifiedName)
409 * Template method which generates class attributes.
411 * @param boolean value which specify whether field is|isn't final
412 * @return string with class attributes and their types
414 def private generateFields(boolean _final) '''
415 «IF properties !== null»
417 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
422 def private generateAugmentField(boolean isPrivate) '''
423 «IF augmentField !== null»
424 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
429 * Template method which generates setter methods
431 * @return string with the setter methods
433 def private generateSetters() '''
434 «FOR field : properties SEPARATOR '\n'»
435 «/* FIXME: generate checkers as simple blocks and embed them directly in setters */»
436 «val restrictions = field.returnType.restrictions»
437 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
438 «IF !restrictions.rangeConstraints.nullOrEmpty»
439 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
440 «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraints)»
443 «IF !restrictions.lengthConstraints.nullOrEmpty»
444 «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraints)»
448 public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
449 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
450 «IF restrictions !== null && (!restrictions.rangeConstraints.nullOrEmpty || !restrictions.lengthConstraints.nullOrEmpty)»
452 «IF !restrictions.rangeConstraints.nullOrEmpty»
453 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
454 «IF field.returnType instanceof ConcreteType»
455 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
457 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
460 «IF !restrictions.lengthConstraints.nullOrEmpty»
461 «IF field.returnType instanceof ConcreteType»
462 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
464 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
470 this.«field.fieldName» = value;
474 «IF augmentField !== null»
476 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentation) {
477 if (augmentation == null) {
478 return remove«augmentField.name.toFirstUpper»(augmentationType);
481 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
482 this.«augmentField.name» = new «HashMap.importedName»<>();
485 this.«augmentField.name».put(augmentationType, augmentation);
489 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
490 if (this.«augmentField.name» instanceof «HashMap.importedName») {
491 this.«augmentField.name».remove(augmentationType);
498 def private CharSequence generateCopyConstructor(boolean impl) '''
499 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
500 «val allProps = new ArrayList(properties)»
501 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
502 «val keyType = type.getKey»
503 «IF isList && keyType !== null»
504 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
505 «Collections.sort(keyProps,
507 return p1.name.compareTo(p2.name)
510 «FOR field : keyProps»
511 «removeProperty(allProps, field.name)»
513 «removeProperty(allProps, "key")»
514 if (base.getKey() == null) {
515 this._key = new «keyType.importedName»(
516 «FOR keyProp : keyProps SEPARATOR ", "»
517 base.«keyProp.getterMethodName»()
520 «FOR field : keyProps»
521 this.«field.fieldName» = base.«field.getterMethodName»();
524 this._key = base.getKey();
525 «FOR field : keyProps»
526 this.«field.fieldName» = _key.«field.getterMethodName»();
530 «FOR field : allProps»
531 this.«field.fieldName» = base.«field.getterMethodName»();
533 «IF augmentField !== null»
535 switch (base.«augmentField.name».size()) {
537 this.«augmentField.name» = «Collections.importedName».emptyMap();
540 final «Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e = base.«augmentField.name».entrySet().iterator().next();
541 this.«augmentField.name» = «Collections.importedName».<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»>singletonMap(e.getKey(), e.getValue());
544 this.«augmentField.name» = new «HashMap.importedName»<>(base.«augmentField.name»);
547 if (base instanceof «type.name»«IMPL») {
548 «type.name»«IMPL» impl = («type.name»«IMPL») base;
549 if (!impl.«augmentField.name».isEmpty()) {
550 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
552 } else if (base instanceof «AugmentationHolder.importedName») {
553 @SuppressWarnings("unchecked")
554 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
555 if (!casted.augmentations().isEmpty()) {
556 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
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»
626 private int hash = 0;
627 private volatile boolean hashValid = false;
630 public int hashCode() {
635 final int prime = 31;
637 «FOR property : properties»
638 «IF property.returnType.name.contains("[")»
639 result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
641 result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
644 «IF augmentField !== null»
645 result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
656 * Template method which generates the method <code>equals()</code>.
658 * @return string with the <code>equals()</code> method definition in JAVA format
660 def protected generateEquals() '''
661 «IF !properties.empty || augmentField !== null»
663 public boolean equals(«Object.importedName» obj) {
667 if (!(obj instanceof «DataObject.importedName»)) {
670 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
673 «type.importedName» other = («type.importedName»)obj;
674 «FOR property : properties»
675 «val fieldName = property.fieldName»
676 «IF property.returnType.name.contains("[")»
677 if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
679 if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
684 «IF augmentField !== null»
685 if (getClass() == obj.getClass()) {
686 // Simple case: we are comparing against self
687 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
688 «val fieldName = augmentField.name»
689 if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
693 // Hard case: compare our augments with presence there...
694 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
695 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
699 // .. and give the other one the chance to do the same
700 if (!obj.equals(this)) {
710 def override generateToString(Collection<GeneratedProperty> properties) '''
711 «IF !(properties === null)»
713 public «String.importedName» toString() {
714 «String.importedName» name = "«type.name» [";
715 «StringBuilder.importedName» builder = new «StringBuilder.importedName» (name);
716 «FOR property : properties SEPARATOR "\n builder.append(\", \");\n}" AFTER " }\n"»
717 if («property.fieldName» != null) {
718 builder.append("«property.fieldName»=");
719 «IF property.returnType.name.contains("[")»
720 builder.append(«Arrays.importedName».toString(«property.fieldName»));
722 builder.append(«property.fieldName»);
725 «IF augmentField !== null»
726 «IF !properties.empty»
727 «««Append comma separator only if it's not there already from previous operation»»»
728 final int builderLength = builder.length();
729 final int builderAdditionalLength = builder.substring(name.length(), builderLength).length();
730 if (builderAdditionalLength > 2 && !builder.substring(builderLength - 2, builderLength).equals(", ")) {
731 builder.append(", ");
734 builder.append("«augmentField.name»=");
735 builder.append(«augmentField.name».values());«"\n"»
736 return builder.append(']').toString();
738 «IF properties.empty»
739 return builder.append(']').toString();
741 return builder.append(']').toString();
748 def implementedInterfaceGetter() '''
750 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
751 return «type.importedName».class;
755 private def createDescription(GeneratedType type) {
757 Class that builds {@link «type.importedName»} instances.
759 @see «type.importedName»
763 override def protected String formatDataForJavaDoc(GeneratedType type) {
764 val typeDescription = createDescription(type)
767 «IF !typeDescription.nullOrEmpty»