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 static extension org.apache.commons.text.StringEscapeUtils.escapeJava
11 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import com.google.common.collect.ImmutableList
15 import java.util.ArrayList
16 import java.util.Collection
17 import java.util.HashMap
18 import java.util.HashSet
22 import java.util.regex.Pattern
23 import org.opendaylight.mdsal.binding.model.api.AnnotationType
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.JavaTypeName
28 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
29 import org.opendaylight.mdsal.binding.model.api.Type
30 import org.opendaylight.mdsal.binding.model.util.TypeConstants
31 import org.opendaylight.mdsal.binding.model.util.Types
32 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
33 import org.opendaylight.yangtools.concepts.Builder
34 import org.opendaylight.yangtools.yang.binding.AugmentationHolder
35 import org.opendaylight.yangtools.yang.binding.CodeHelpers
36 import org.opendaylight.yangtools.yang.binding.DataObject
39 * Template for generating JAVA builder classes.
41 class BuilderTemplate extends AbstractBuilderTemplate {
43 * Constant used as suffix for builder name.
45 public static val BUILDER = "Builder";
47 static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
50 * Constructs new instance of this class.
51 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
53 new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
55 super(genType, targetType, properties, augmentType, keyType)
58 override isLocalInnerClass(JavaTypeName name) {
59 // Builders do not have inner types
64 * Template method which generates JAVA class body for builder class and for IMPL class.
66 * @return string with JAVA source code
69 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
70 «targetType.annotations.generateDeprecatedAnnotation»
71 public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
73 «generateFields(false)»
75 «constantsDeclarations()»
77 «IF augmentType !== null»
78 «generateAugmentField()»
81 «generateConstructorsFromIfcs()»
83 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
85 «generateMethodFieldsFrom()»
87 «generateGetters(false)»
88 «IF augmentType !== null»
90 «generateAugmentation()»
95 @«OVERRIDE.importedName»
96 public «targetType.name» build() {
97 return new «type.enclosedTypes.get(0).importedName»(this);
100 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
104 override generateDeprecatedAnnotation(AnnotationType ann) {
105 val forRemoval = ann.getParameter("forRemoval")
106 if (forRemoval !== null) {
107 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
109 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
113 * Generate default constructor and constructor for every implemented interface from uses statements.
115 def private generateConstructorsFromIfcs() '''
116 public «type.name»() {
118 «IF (!(targetType instanceof GeneratedTransferObject))»
119 «FOR impl : targetType.implements»
120 «generateConstructorFromIfc(impl)»
126 * Generate constructor with argument of given type.
128 def private Object generateConstructorFromIfc(Type impl) '''
129 «IF (impl instanceof GeneratedType)»
130 «IF impl.hasNonDefaultMethods»
131 public «type.name»(«impl.fullyQualifiedName» arg) {
132 «printConstructorPropertySetter(impl)»
135 «FOR implTypeImplement : impl.implements»
136 «generateConstructorFromIfc(implTypeImplement)»
141 def private Object printConstructorPropertySetter(Type implementedIfc) '''
142 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
143 «val ifc = implementedIfc as GeneratedType»
144 «FOR getter : ifc.nonDefaultMethods»
145 «IF BindingMapping.isGetterMethodName(getter.name)»
146 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
149 «FOR impl : ifc.implements»
150 «printConstructorPropertySetter(impl)»
156 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
158 def private generateMethodFieldsFrom() '''
159 «IF (!(targetType instanceof GeneratedTransferObject))»
160 «IF targetType.hasImplementsFromUses»
161 «val List<Type> done = targetType.getBaseIfcs»
162 «generateMethodFieldsFromComment(targetType)»
163 public void fieldsFrom(«DataObject.importedName» arg) {
164 boolean isValidArg = false;
165 «FOR impl : targetType.getAllIfcs»
166 «generateIfCheck(impl, done)»
168 «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
174 def private generateMethodFieldsFromComment(GeneratedType type) '''
176 * Set fields from given grouping argument. Valid argument is instance of one of following types:
178 «FOR impl : type.getAllIfcs»
179 * <li>«impl.fullyQualifiedName»</li>
183 * @param arg grouping object
184 * @throws IllegalArgumentException if given argument is none of valid types
189 * Method is used to find out if given type implements any interface from uses.
191 def boolean hasImplementsFromUses(GeneratedType type) {
193 for (impl : type.getAllIfcs) {
194 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
201 def private generateIfCheck(Type impl, List<Type> done) '''
202 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
203 «val implType = impl as GeneratedType»
204 if (arg instanceof «implType.fullyQualifiedName») {
205 «printPropertySetter(implType)»
211 def private printPropertySetter(Type implementedIfc) '''
212 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
213 «val ifc = implementedIfc as GeneratedType»
214 «FOR getter : ifc.nonDefaultMethods»
215 «IF BindingMapping.isGetterMethodName(getter.name)»
216 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
222 private def List<Type> getBaseIfcs(GeneratedType type) {
223 val List<Type> baseIfcs = new ArrayList();
224 for (ifc : type.implements) {
225 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
232 private def Set<Type> getAllIfcs(Type type) {
233 val Set<Type> baseIfcs = new HashSet()
234 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
235 val ifc = type as GeneratedType
236 for (impl : ifc.implements) {
237 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
240 baseIfcs.addAll(impl.getAllIfcs)
246 private def List<String> toListOfNames(Collection<Type> types) {
247 val List<String> names = new ArrayList
249 names.add(type.fullyQualifiedName)
254 def private constantsDeclarations() '''
255 «FOR c : type.getConstantDefinitions»
256 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
257 «val cValue = c.value as Map<String, String>»
258 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
259 «IF cValue.size == 1»
260 «val firstEntry = cValue.entrySet.iterator.next»
261 private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«firstEntry.key.escapeJava»");
262 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
264 private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
265 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
266 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
267 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
275 def private generateListSetter(GeneratedProperty field, Type actualType) '''
276 «val restrictions = restrictionsForSetter(actualType)»
277 «IF restrictions !== null»
278 «generateCheckers(field, restrictions, actualType)»
280 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
281 «IF restrictions !== null»
282 if (values != null) {
283 for («actualType.getFullyQualifiedName» value : values) {
284 «checkArgument(field, restrictions, actualType, "value")»
288 this.«field.fieldName» = values;
294 def private generateSetter(GeneratedProperty field, Type actualType) '''
295 «val restrictions = restrictionsForSetter(actualType)»
296 «IF restrictions !== null»
298 «generateCheckers(field, restrictions, actualType)»
301 «val setterName = "set" + field.getName.toFirstUpper»
302 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
303 «IF restrictions !== null»
305 «checkArgument(field, restrictions, actualType, "value")»
308 this.«field.fieldName» = value;
311 «val uintType = UINT_TYPES.get(field.returnType)»
312 «IF uintType !== null»
315 * Utility migration setter.
317 * @param value field value in legacy type
318 * @return this builder
319 * @deprecated Use {#link «setterName»(«field.returnType.importedJavadocName»)} instead.
321 @Deprecated(forRemoval = true)
322 public «type.getName» «setterName»(final «uintType.importedName» value) {
323 return «setterName»(«CodeHelpers.importedName».compatUint(value));
328 private def Type getActualType(ParameterizedType ptype) {
329 return ptype.getActualTypeArguments.get(0)
333 * Template method which generates setter methods
335 * @return string with the setter methods
337 def private generateSetters() '''
338 «IF keyType !== null»
339 public «type.getName» withKey(final «keyType.importedName» key) {
344 «FOR property : properties»
345 «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
346 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
348 «generateSetter(property, property.returnType)»
352 «IF augmentType !== null»
353 «val augmentTypeRef = augmentType.importedName»
354 public «type.name» add«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
355 if (augmentationValue == null) {
356 return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
359 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
360 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
363 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
367 public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType) {
368 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
369 this.«AUGMENTATION_FIELD».remove(augmentationType);
376 private def createDescription(GeneratedType targetType) {
377 val target = type.importedName
379 Class that builds {@link «target»} instances. Overall design of the class is that of a
380 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
383 In general, this class is supposed to be used like this template:
386 «target» createTarget(int fooXyzzy, int barBaz) {
387 return new «target»Builder()
388 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
389 .setBar(new BarBuilder().setBaz(barBaz).build())
396 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
397 worrying about synchronization issues.
400 As a side note: method chaining results in:
402 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
403 on the stack, so further method invocations just need to fill method arguments for the next method
404 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
405 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
407 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
408 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
409 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
414 @see «Builder.importedName»
418 override protected String formatDataForJavaDoc(GeneratedType type) {
419 val typeDescription = createDescription(type)
422 «IF !typeDescription.nullOrEmpty»
428 private def generateAugmentation() '''
429 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
430 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
431 return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
435 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
436 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
437 «FOR field : keyProps»
438 this.«field.fieldName» = base.«field.getterMethodName»();
442 override protected generateCopyAugmentation(Type implType) {
443 val augmentationHolderRef = AugmentationHolder.importedName
444 val typeRef = targetType.importedName
445 val hashMapRef = HashMap.importedName
446 val augmentTypeRef = augmentType.importedName
448 if (base instanceof «augmentationHolderRef») {
449 @SuppressWarnings("unchecked")
450 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
451 if (!aug.isEmpty()) {
452 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
458 private static def hasNonDefaultMethods(GeneratedType type) {
459 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
462 private static def nonDefaultMethods(GeneratedType type) {
463 type.methodDefinitions.filter([def | !def.isDefault])