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.base.MoreObjects
11 import com.google.common.collect.ImmutableMap
12 import com.google.common.collect.ImmutableSortedSet
13 import java.util.ArrayList
14 import java.util.Arrays
15 import java.util.Collection
16 import java.util.Collections
17 import java.util.HashMap
18 import java.util.HashSet
19 import java.util.LinkedHashSet
22 import java.util.Objects
24 import org.opendaylight.mdsal.binding.model.api.ConcreteType
25 import org.opendaylight.mdsal.binding.model.api.GeneratedProperty
26 import org.opendaylight.mdsal.binding.model.api.GeneratedTransferObject
27 import org.opendaylight.mdsal.binding.model.api.GeneratedType
28 import org.opendaylight.mdsal.binding.model.api.MethodSignature
29 import org.opendaylight.mdsal.binding.model.api.Type
30 import org.opendaylight.mdsal.binding.model.util.ReferencedTypeImpl
31 import org.opendaylight.mdsal.binding.model.util.Types
32 import org.opendaylight.mdsal.binding.model.util.generated.type.builder.CodegenGeneratedTOBuilder
33 import org.opendaylight.yangtools.concepts.Builder
34 import org.opendaylight.yangtools.yang.binding.Augmentable
35 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
36 import org.opendaylight.yangtools.yang.binding.CodeHelpers
37 import org.opendaylight.yangtools.yang.binding.DataObject
38 import org.opendaylight.yangtools.yang.binding.Identifiable
41 * Template for generating JAVA builder classes.
44 class BuilderTemplate extends BaseTemplate {
47 * Constant with the name of the concrete method.
49 val static GET_AUGMENTATION_METHOD_NAME = "getAugmentation"
52 * Constant with the suffix for builder classes.
54 val static BUILDER = 'Builder'
57 * Constant with the name of the BuilderFor interface
59 val static BUILDERFOR = Builder.simpleName;
62 * Constant with suffix for the classes which are generated from the builder classes.
64 val static IMPL = 'Impl'
67 * Generated property is set if among methods is found one with the name GET_AUGMENTATION_METHOD_NAME
69 var GeneratedProperty augmentField
72 * Set of class attributes (fields) which are derived from the getter methods names
74 val Set<GeneratedProperty> properties
76 private static val METHOD_COMPARATOR = new AlphabeticallyTypeMemberComparator<MethodSignature>();
79 * Constructs new instance of this class.
80 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
82 new(GeneratedType genType) {
84 this.properties = propertiesFromMethods(createMethods)
85 addImport(Builder.simpleName, Builder.package.name)
89 * Returns set of method signature instances which contains all the methods of the <code>genType</code>
90 * and all the methods of the implemented interfaces.
92 * @returns set of method signature instances
94 def private Set<MethodSignature> createMethods() {
95 val Set<MethodSignature> methods = new LinkedHashSet();
96 methods.addAll(type.methodDefinitions)
97 collectImplementedMethods(methods, type.implements)
98 val Set<MethodSignature> sortedMethods = ImmutableSortedSet.orderedBy(METHOD_COMPARATOR).addAll(methods).build()
104 * Adds to the <code>methods</code> set all the methods of the <code>implementedIfcs</code>
105 * and recursively their implemented interfaces.
107 * @param methods set of method signatures
108 * @param implementedIfcs list of implemented interfaces
110 def private void collectImplementedMethods(Set<MethodSignature> methods, List<Type> implementedIfcs) {
111 if (implementedIfcs === null || implementedIfcs.empty) {
114 for (implementedIfc : implementedIfcs) {
115 if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) {
116 val ifc = implementedIfc as GeneratedType
117 methods.addAll(ifc.methodDefinitions)
118 collectImplementedMethods(methods, ifc.implements)
119 } else if (implementedIfc.fullyQualifiedName == Augmentable.name) {
120 for (m : Augmentable.methods) {
121 if (m.name == GET_AUGMENTATION_METHOD_NAME) {
122 val fullyQualifiedName = m.returnType.name
123 val pkg = fullyQualifiedName.package
124 val name = fullyQualifiedName.name
125 val tmpGenTO = new CodegenGeneratedTOBuilder(pkg, name)
126 val refType = new ReferencedTypeImpl(pkg, name)
127 val generic = new ReferencedTypeImpl(type.packageName, type.name)
128 val parametrizedReturnType = Types.parameterizedTypeFor(refType, generic)
129 tmpGenTO.addMethod(m.name).setReturnType(parametrizedReturnType)
130 augmentField = tmpGenTO.toInstance.methodDefinitions.first.propertyFromGetter
138 * Returns the first element of the list <code>elements</code>.
140 * @param list of elements
142 def private <E> first(List<E> elements) {
147 * Returns the name of the package from <code>fullyQualifiedName</code>.
149 * @param fullyQualifiedName string with fully qualified type name (package + type)
150 * @return string with the package name
152 def private String getPackage(String fullyQualifiedName) {
153 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
154 return if (lastDotIndex == -1) "" else fullyQualifiedName.substring(0, lastDotIndex)
158 * Returns the name of tye type from <code>fullyQualifiedName</code>
160 * @param fullyQualifiedName string with fully qualified type name (package + type)
161 * @return string with the name of the type
163 def private String getName(String fullyQualifiedName) {
164 val lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT)
165 return if (lastDotIndex == -1) fullyQualifiedName else fullyQualifiedName.substring(lastDotIndex + 1)
169 * Creates set of generated property instances from getter <code>methods</code>.
171 * @param set of method signature instances which should be transformed to list of properties
172 * @return set of generated property instances which represents the getter <code>methods</code>
174 def private propertiesFromMethods(Collection<MethodSignature> methods) {
175 if (methods === null || methods.isEmpty()) {
176 return Collections.emptySet
178 val Set<GeneratedProperty> result = new LinkedHashSet
180 val createdField = m.propertyFromGetter
181 if (createdField !== null) {
182 result.add(createdField)
189 * Creates generated property instance from the getter <code>method</code> name and return type.
191 * @param method method signature from which is the method name and return type obtained
192 * @return generated property instance for the getter <code>method</code>
193 * @throws IllegalArgumentException<ul>
194 * <li>if the <code>method</code> equals <code>null</code></li>
195 * <li>if the name of the <code>method</code> equals <code>null</code></li>
196 * <li>if the name of the <code>method</code> is empty</li>
197 * <li>if the return type of the <code>method</code> equals <code>null</code></li>
200 def private GeneratedProperty propertyFromGetter(MethodSignature method) {
201 if (method === null || method.name === null || method.name.empty || method.returnType === null) {
202 throw new IllegalArgumentException("Method, method name, method return type reference cannot be NULL or empty!")
205 if (Types.BOOLEAN.equals(method.returnType)) {
208 if (method.name.startsWith(prefix)) {
209 val fieldName = method.getName().substring(prefix.length()).toFirstLower
210 val tmpGenTO = new CodegenGeneratedTOBuilder("foo", "foo")
211 tmpGenTO.addProperty(fieldName).setReturnType(method.returnType)
212 return tmpGenTO.toInstance.properties.first
216 override isLocalInnerClass(String importedTypePackageName) {
217 // Builders do not have inner types
222 * Template method which generates JAVA class body for builder class and for IMPL class.
224 * @return string with JAVA source code
227 «wrapToDocumentation(formatDataForJavaDoc(type))»
228 public class «type.name»«BUILDER» implements «BUILDERFOR»<«type.importedName»> {
230 «generateFields(false)»
232 «generateAugmentField(false)»
234 «generateConstructorsFromIfcs(type)»
236 «generateCopyConstructor(false)»
238 «generateMethodFieldsFrom(type)»
240 «generateGetters(false)»
245 public «type.name» build() {
246 return new «type.name»«IMPL»(this);
249 private static final class «type.name»«IMPL» implements «type.name» {
251 «implementedInterfaceGetter»
253 «generateFields(true)»
255 «generateAugmentField(true)»
257 «generateCopyConstructor(true)»
259 «generateGetters(true)»
265 «generateToString(properties)»
272 * Generate default constructor and constructor for every implemented interface from uses statements.
274 def private generateConstructorsFromIfcs(Type type) '''
275 public «type.name»«BUILDER»() {
277 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
278 «val ifc = type as GeneratedType»
279 «FOR impl : ifc.implements»
280 «generateConstructorFromIfc(impl)»
286 * Generate constructor with argument of given type.
288 def private Object generateConstructorFromIfc(Type impl) '''
289 «IF (impl instanceof GeneratedType)»
290 «IF !(impl.methodDefinitions.empty)»
291 public «type.name»«BUILDER»(«impl.fullyQualifiedName» arg) {
292 «printConstructorPropertySetter(impl)»
295 «FOR implTypeImplement : impl.implements»
296 «generateConstructorFromIfc(implTypeImplement)»
301 def private Object printConstructorPropertySetter(Type implementedIfc) '''
302 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
303 «val ifc = implementedIfc as GeneratedType»
304 «FOR getter : ifc.methodDefinitions»
305 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
307 «FOR impl : ifc.implements»
308 «printConstructorPropertySetter(impl)»
314 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
316 def private generateMethodFieldsFrom(Type type) '''
317 «IF (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject))»
318 «val ifc = type as GeneratedType»
319 «IF ifc.hasImplementsFromUses»
320 «val List<Type> done = ifc.getBaseIfcs»
321 «generateMethodFieldsFromComment(ifc)»
322 public void fieldsFrom(«DataObject.importedName» arg) {
323 boolean isValidArg = false;
324 «FOR impl : ifc.getAllIfcs»
325 «generateIfCheck(impl, done)»
327 «CodeHelpers.importedName».validValue(isValidArg, arg, "«ifc.getAllIfcs.toListOfNames»");
333 def private generateMethodFieldsFromComment(GeneratedType type) '''
335 * Set fields from given grouping argument. Valid argument is instance of one of following types:
337 «FOR impl : type.getAllIfcs»
338 * <li>«impl.fullyQualifiedName»</li>
342 * @param arg grouping object
343 * @throws IllegalArgumentException if given argument is none of valid types
348 * Method is used to find out if given type implements any interface from uses.
350 def boolean hasImplementsFromUses(GeneratedType type) {
352 for (impl : type.getAllIfcs) {
353 if ((impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)) {
360 def private generateIfCheck(Type impl, List<Type> done) '''
361 «IF (impl instanceof GeneratedType) && !((impl as GeneratedType).methodDefinitions.empty)»
362 «val implType = impl as GeneratedType»
363 if (arg instanceof «implType.fullyQualifiedName») {
364 «printPropertySetter(implType)»
370 def private printPropertySetter(Type implementedIfc) '''
371 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
372 «val ifc = implementedIfc as GeneratedType»
373 «FOR getter : ifc.methodDefinitions»
374 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
379 private def List<Type> getBaseIfcs(GeneratedType type) {
380 val List<Type> baseIfcs = new ArrayList();
381 for (ifc : type.implements) {
382 if (ifc instanceof GeneratedType && !(ifc as GeneratedType).methodDefinitions.empty) {
389 private def Set<Type> getAllIfcs(Type type) {
390 val Set<Type> baseIfcs = new HashSet()
391 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
392 val ifc = type as GeneratedType
393 for (impl : ifc.implements) {
394 if (impl instanceof GeneratedType && !(impl as GeneratedType).methodDefinitions.empty) {
397 baseIfcs.addAll(impl.getAllIfcs)
403 private def List<String> toListOfNames(Collection<Type> types) {
404 val List<String> names = new ArrayList
406 names.add(type.fullyQualifiedName)
412 * Template method which generates class attributes.
414 * @param boolean value which specify whether field is|isn't final
415 * @return string with class attributes and their types
417 def private generateFields(boolean _final) '''
418 «IF properties !== null»
420 private«IF _final» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
425 def private generateAugmentField(boolean isPrivate) '''
426 «IF augmentField !== null»
427 «IF isPrivate»private «ENDIF»«Map.importedName»<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> «augmentField.name» = «Collections.importedName».emptyMap();
432 * Template method which generates setter methods
434 * @return string with the setter methods
436 def private generateSetters() '''
437 «FOR field : properties SEPARATOR '\n'»
438 «/* FIXME: generate checkers as simple blocks and embed them directly in setters */»
439 «val restrictions = field.returnType.restrictions»
440 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
441 «IF restrictions.rangeConstraint.present»
442 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
443 «rangeGenerator.generateRangeChecker(field.name.toFirstUpper, restrictions.rangeConstraint.get, this)»
446 «IF restrictions.lengthConstraint.present»
447 «LengthGenerator.generateLengthChecker(field.fieldName.toString, field.returnType, restrictions.lengthConstraint.get, this)»
451 public «type.name»«BUILDER» set«field.name.toFirstUpper»(final «field.returnType.importedName» value) {
452 «IF !(field.returnType instanceof GeneratedType) && restrictions !== null»
453 «IF restrictions !== null && (restrictions.rangeConstraint.present || restrictions.lengthConstraint.present)»
455 «IF restrictions.rangeConstraint.present»
456 «val rangeGenerator = AbstractRangeGenerator.forType(field.returnType)»
457 «IF field.returnType instanceof ConcreteType»
458 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value")»
460 «rangeGenerator.generateRangeCheckerCall(field.name.toFirstUpper, "value.getValue()")»
463 «IF restrictions.lengthConstraint.present»
464 «IF field.returnType instanceof ConcreteType»
465 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value")»
467 «LengthGenerator.generateLengthCheckerCall(field.fieldName.toString, "value.getValue()")»
473 this.«field.fieldName» = value;
477 «IF augmentField !== null»
479 public «type.name»«BUILDER» add«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType, «augmentField.returnType.importedName» augmentationValue) {
480 if (augmentationValue == null) {
481 return remove«augmentField.name.toFirstUpper»(augmentationType);
484 if (!(this.«augmentField.name» instanceof «HashMap.importedName»)) {
485 this.«augmentField.name» = new «HashMap.importedName»<>();
488 this.«augmentField.name».put(augmentationType, augmentationValue);
492 public «type.name»«BUILDER» remove«augmentField.name.toFirstUpper»(«Class.importedName»<? extends «augmentField.returnType.importedName»> augmentationType) {
493 if (this.«augmentField.name» instanceof «HashMap.importedName») {
494 this.«augmentField.name».remove(augmentationType);
501 def private CharSequence generateCopyConstructor(boolean impl) '''
502 «IF impl»private«ELSE»public«ENDIF» «type.name»«IF impl»«IMPL»«ELSE»«BUILDER»«ENDIF»(«type.name»«IF impl»«BUILDER»«ENDIF» base) {
503 «val allProps = new ArrayList(properties)»
504 «val isList = implementsIfc(type, Types.parameterizedTypeFor(Types.typeForClass(Identifiable), type))»
505 «val keyType = type.getKey»
506 «IF isList && keyType !== null»
507 «val keyProps = new ArrayList((keyType as GeneratedTransferObject).properties)»
508 «Collections.sort(keyProps,
510 return p1.name.compareTo(p2.name)
513 «FOR field : keyProps»
514 «removeProperty(allProps, field.name)»
516 «removeProperty(allProps, "key")»
517 if (base.getKey() == null) {
518 this._key = new «keyType.importedName»(
519 «FOR keyProp : keyProps SEPARATOR ", "»
520 base.«keyProp.getterMethodName»()
523 «FOR field : keyProps»
524 this.«field.fieldName» = base.«field.getterMethodName»();
527 this._key = base.getKey();
528 «FOR field : keyProps»
529 this.«field.fieldName» = _key.«field.getterMethodName»();
533 «FOR field : allProps»
534 this.«field.fieldName» = base.«field.getterMethodName»();
536 «IF augmentField !== null»
538 this.«augmentField.name» = «ImmutableMap.importedName».copyOf(base.«augmentField.name»);
540 if (base instanceof «type.name»«IMPL») {
541 «type.name»«IMPL» impl = («type.name»«IMPL») base;
542 if (!impl.«augmentField.name».isEmpty()) {
543 this.«augmentField.name» = new «HashMap.importedName»<>(impl.«augmentField.name»);
545 } else if (base instanceof «AugmentationHolder.importedName») {
546 @SuppressWarnings("unchecked")
547 «AugmentationHolder.importedName»<«type.importedName»> casted =(«AugmentationHolder.importedName»<«type.importedName»>) base;
548 if (!casted.augmentations().isEmpty()) {
549 this.«augmentField.name» = new «HashMap.importedName»<>(casted.augmentations());
557 private def boolean implementsIfc(GeneratedType type, Type impl) {
558 for (Type ifc : type.implements) {
559 if (ifc.equals(impl)) {
566 private def Type getKey(GeneratedType type) {
567 for (m : type.methodDefinitions) {
568 if ("getKey".equals(m.name)) {
575 private def void removeProperty(Collection<GeneratedProperty> props, String name) {
576 var GeneratedProperty toRemove = null
578 if (p.name.equals(name)) {
582 if (toRemove !== null) {
583 props.remove(toRemove);
588 * Template method which generate getter methods for IMPL class.
590 * @return string with getter methods
592 def private generateGetters(boolean addOverride) '''
593 «IF !properties.empty»
594 «FOR field : properties SEPARATOR '\n'»
595 «IF addOverride»@Override«ENDIF»
599 «IF augmentField !== null»
601 @SuppressWarnings("unchecked")
602 «IF addOverride»@Override«ENDIF»
603 public <E extends «augmentField.returnType.importedName»> E get«augmentField.name.toFirstUpper»(«Class.importedName»<E> augmentationType) {
604 return (E) «augmentField.name».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
610 * Template method which generates the method <code>hashCode()</code>.
612 * @return string with the <code>hashCode()</code> method definition in JAVA format
614 def protected generateHashCode() '''
615 «IF !properties.empty || augmentField !== null»
616 private int hash = 0;
617 private volatile boolean hashValid = false;
620 public int hashCode() {
625 final int prime = 31;
627 «FOR property : properties»
628 «IF property.returnType.name.contains("[")»
629 result = prime * result + «Arrays.importedName».hashCode(«property.fieldName»);
631 result = prime * result + «Objects.importedName».hashCode(«property.fieldName»);
634 «IF augmentField !== null»
635 result = prime * result + «Objects.importedName».hashCode(«augmentField.name»);
646 * Template method which generates the method <code>equals()</code>.
648 * @return string with the <code>equals()</code> method definition in JAVA format
650 def protected generateEquals() '''
651 «IF !properties.empty || augmentField !== null»
653 public boolean equals(«Object.importedName» obj) {
657 if (!(obj instanceof «DataObject.importedName»)) {
660 if (!«type.importedName».class.equals(((«DataObject.importedName»)obj).getImplementedInterface())) {
663 «type.importedName» other = («type.importedName»)obj;
664 «FOR property : properties»
665 «val fieldName = property.fieldName»
666 «IF property.returnType.name.contains("[")»
667 if (!«Arrays.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
669 if (!«Objects.importedName».equals(«fieldName», other.«property.getterMethodName»())) {
674 «IF augmentField !== null»
675 if (getClass() == obj.getClass()) {
676 // Simple case: we are comparing against self
677 «type.name»«IMPL» otherImpl = («type.name»«IMPL») obj;
678 «val fieldName = augmentField.name»
679 if (!«Objects.importedName».equals(«fieldName», otherImpl.«fieldName»)) {
683 // Hard case: compare our augments with presence there...
684 for («Map.importedName».Entry<«Class.importedName»<? extends «augmentField.returnType.importedName»>, «augmentField.returnType.importedName»> e : «augmentField.name».entrySet()) {
685 if (!e.getValue().equals(other.getAugmentation(e.getKey()))) {
689 // .. and give the other one the chance to do the same
690 if (!obj.equals(this)) {
700 def override generateToString(Collection<GeneratedProperty> properties) '''
701 «IF properties !== null»
703 public «String.importedName» toString() {
704 final «MoreObjects.importedName».ToStringHelper helper = «MoreObjects.importedName».toStringHelper("«type.name»");
705 «FOR property : properties»
706 «CodeHelpers.importedName».appendValue(helper, "«property.fieldName»", «property.fieldName»);
708 «IF augmentField !== null»
709 «CodeHelpers.importedName».appendValue(helper, "«augmentField.name»", «augmentField.name».values());
711 return helper.toString();
716 def implementedInterfaceGetter() '''
718 public «Class.importedName»<«type.importedName»> getImplementedInterface() {
719 return «type.importedName».class;
723 private def createDescription(GeneratedType type) {
725 Class that builds {@link «type.importedName»} instances.
727 @see «type.importedName»
731 override def protected String formatDataForJavaDoc(GeneratedType type) {
732 val typeDescription = createDescription(type)
735 «IF !typeDescription.nullOrEmpty»