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.model.util.BindingTypes.DATA_OBJECT
12 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTABLE_AUGMENTATION_NAME
13 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.AUGMENTATION_FIELD
14 import static org.opendaylight.mdsal.binding.spec.naming.BindingMapping.DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME
16 import com.google.common.collect.ImmutableList
17 import java.util.ArrayList
18 import java.util.Collection
19 import java.util.HashSet
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
36 * Template for generating JAVA builder classes.
38 class BuilderTemplate extends AbstractBuilderTemplate {
40 * Constant used as suffix for builder name.
42 package static val BUILDER_STR = "Builder";
44 static val BUILDER = JavaTypeName.create(Builder)
47 * Constructs new instance of this class.
48 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
50 new(GeneratedType genType, GeneratedType targetType, Type keyType) {
51 super(genType, targetType, keyType)
54 override isLocalInnerClass(JavaTypeName name) {
55 // Builders do not have inner types
60 * Template method which generates JAVA class body for builder class and for IMPL class.
62 * @return string with JAVA source code
65 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
66 «targetType.annotations.generateDeprecatedAnnotation»
67 public class «type.name» implements «BUILDER.importedName»<«targetType.importedName»> {
69 «generateFields(false)»
71 «constantsDeclarations()»
73 «IF augmentType !== null»
74 «generateAugmentField()»
77 «generateConstructorsFromIfcs()»
79 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
81 «generateMethodFieldsFrom()»
83 «generateGetters(false)»
84 «IF augmentType !== null»
86 «generateAugmentation()»
91 @«OVERRIDE.importedName»
92 public «targetType.name» build() {
93 return new «type.enclosedTypes.get(0).importedName»(this);
96 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
100 override generateDeprecatedAnnotation(AnnotationType ann) {
101 val forRemoval = ann.getParameter("forRemoval")
102 if (forRemoval !== null) {
103 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
105 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
109 * Generate default constructor and constructor for every implemented interface from uses statements.
111 def private generateConstructorsFromIfcs() '''
112 public «type.name»() {
114 «IF (!(targetType instanceof GeneratedTransferObject))»
115 «FOR impl : targetType.implements»
116 «generateConstructorFromIfc(impl)»
122 * Generate constructor with argument of given type.
124 def private Object generateConstructorFromIfc(Type impl) '''
125 «IF (impl instanceof GeneratedType)»
126 «IF impl.hasNonDefaultMethods»
127 public «type.name»(«impl.fullyQualifiedName» arg) {
128 «printConstructorPropertySetter(impl)»
131 «FOR implTypeImplement : impl.implements»
132 «generateConstructorFromIfc(implTypeImplement)»
137 def private Object printConstructorPropertySetter(Type implementedIfc) '''
138 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
139 «val ifc = implementedIfc as GeneratedType»
140 «FOR getter : ifc.nonDefaultMethods»
141 «IF BindingMapping.isGetterMethodName(getter.name)»
142 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
145 «FOR impl : ifc.implements»
146 «printConstructorPropertySetter(impl)»
152 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
154 def private generateMethodFieldsFrom() '''
155 «IF (!(targetType instanceof GeneratedTransferObject))»
156 «IF targetType.hasImplementsFromUses»
157 «val List<Type> done = targetType.getBaseIfcs»
158 «generateMethodFieldsFromComment(targetType)»
159 public void fieldsFrom(«DATA_OBJECT.importedName» arg) {
160 boolean isValidArg = false;
161 «FOR impl : targetType.getAllIfcs»
162 «generateIfCheck(impl, done)»
164 «CODEHELPERS.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
170 def private generateMethodFieldsFromComment(GeneratedType type) '''
172 * Set fields from given grouping argument. Valid argument is instance of one of following types:
174 «FOR impl : type.getAllIfcs»
175 * <li>«impl.fullyQualifiedName»</li>
179 * @param arg grouping object
180 * @throws IllegalArgumentException if given argument is none of valid types
185 * Method is used to find out if given type implements any interface from uses.
187 def boolean hasImplementsFromUses(GeneratedType type) {
189 for (impl : type.getAllIfcs) {
190 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
197 def private generateIfCheck(Type impl, List<Type> done) '''
198 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
199 «val implType = impl as GeneratedType»
200 if (arg instanceof «implType.fullyQualifiedName») {
201 «printPropertySetter(implType)»
207 def private printPropertySetter(Type implementedIfc) '''
208 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
209 «val ifc = implementedIfc as GeneratedType»
210 «FOR getter : ifc.nonDefaultMethods»
211 «IF BindingMapping.isGetterMethodName(getter.name)»
212 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
218 private def List<Type> getBaseIfcs(GeneratedType type) {
219 val List<Type> baseIfcs = new ArrayList();
220 for (ifc : type.implements) {
221 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
228 private def Set<Type> getAllIfcs(Type type) {
229 val Set<Type> baseIfcs = new HashSet()
230 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
231 val ifc = type as GeneratedType
232 for (impl : ifc.implements) {
233 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
236 baseIfcs.addAll(impl.getAllIfcs)
242 private def List<String> toListOfNames(Collection<Type> types) {
243 val List<String> names = new ArrayList
245 names.add(type.fullyQualifiedName)
250 def private constantsDeclarations() '''
251 «FOR c : type.getConstantDefinitions»
252 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
253 «val cValue = c.value as Map<String, String>»
254 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
255 «val jurPatternRef = JUR_PATTERN.importedName»
256 «IF cValue.size == 1»
257 «val firstEntry = cValue.entrySet.iterator.next»
258 private static final «jurPatternRef» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «jurPatternRef».compile("«firstEntry.key.escapeJava»");
259 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
261 private static final «jurPatternRef»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CODEHELPERS.importedName».compilePatterns(«ImmutableList.importedName».of(
262 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
263 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
264 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
272 def private generateSetter(GeneratedProperty field) {
273 val returnType = field.returnType
274 if (returnType instanceof ParameterizedType) {
275 if (Types.isListType(returnType)) {
276 return generateListSetter(field, returnType.actualTypeArguments.get(0))
277 } else if (Types.isMapType(returnType)) {
278 return generateMapSetter(field, returnType.actualTypeArguments.get(1))
281 return generateSimpleSetter(field, returnType)
284 def private generateListSetter(GeneratedProperty field, Type actualType) '''
285 «val restrictions = restrictionsForSetter(actualType)»
286 «IF restrictions !== null»
287 «generateCheckers(field, restrictions, actualType)»
289 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
290 «IF restrictions !== null»
291 if (values != null) {
292 for («actualType.importedName» value : values) {
293 «checkArgument(field, restrictions, actualType, "value")»
297 this.«field.fieldName» = values;
303 // FIXME: MDSAL-540: remove the migration setter
304 def private generateMapSetter(GeneratedProperty field, Type actualType) '''
305 «val restrictions = restrictionsForSetter(actualType)»
306 «val actualTypeRef = actualType.importedName»
307 «val setterName = "set" + field.name.toFirstUpper»
308 «IF restrictions !== null»
309 «generateCheckers(field, restrictions, actualType)»
311 public «type.getName» «setterName»(final «field.returnType.importedName» values) {
312 «IF restrictions !== null»
313 if (values != null) {
314 for («actualTypeRef» value : values.values()) {
315 «checkArgument(field, restrictions, actualType, "value")»
319 this.«field.fieldName» = values;
324 * Utility migration setter.
326 * <b>IMPORTANT NOTE</b>: This method does not completely match previous mechanics, as the list is processed as
327 * during this method's execution. Any future modifications of the list are <b>NOT</b>
328 * reflected in this builder nor its products.
330 * @param values Legacy List of values
331 * @return this builder
332 * @throws IllegalArgumentException if the list contains entries with the same key
333 * @throws NullPointerException if the list contains a null entry
334 * @deprecated Use {#link #«setterName»(«JU_MAP.importedName»)} instead.
336 @«DEPRECATED.importedName»(forRemoval = true)
337 public «type.getName» «setterName»(final «JU_LIST.importedName»<«actualTypeRef»> values) {
338 return «setterName»(«CODEHELPERS.importedName».compatMap(values));
342 def private generateSimpleSetter(GeneratedProperty field, Type actualType) '''
343 «val restrictions = restrictionsForSetter(actualType)»
344 «IF restrictions !== null»
346 «generateCheckers(field, restrictions, actualType)»
349 «val setterName = "set" + field.getName.toFirstUpper»
350 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
351 «IF restrictions !== null»
353 «checkArgument(field, restrictions, actualType, "value")»
356 this.«field.fieldName» = value;
359 «val uintType = UINT_TYPES.get(field.returnType)»
360 «IF uintType !== null»
363 * Utility migration setter.
365 * @param value field value in legacy type
366 * @return this builder
367 * @deprecated Use {#link «setterName»(«field.returnType.importedJavadocName»)} instead.
369 @Deprecated(forRemoval = true)
370 public «type.getName» «setterName»(final «uintType.importedName» value) {
371 return «setterName»(«CODEHELPERS.importedName».compatUint(value));
377 * Template method which generates setter methods
379 * @return string with the setter methods
381 def private generateSetters() '''
382 «IF keyType !== null»
383 public «type.getName» withKey(final «keyType.importedName» key) {
388 «FOR property : properties»
389 «generateSetter(property)»
392 «IF augmentType !== null»
393 «val augmentTypeRef = augmentType.importedName»
394 «val jlClassRef = CLASS.importedName»
395 «val hashMapRef = JU_HASHMAP.importedName»
397 * Add an augmentation to this builder's product.
399 * @param augmentation augmentation to be added
400 * @return this builder
401 * @throws NullPointerException if {@code augmentation} is null
403 public «type.name» addAugmentation(«augmentTypeRef» augmentation) {
404 «jlClassRef»<? extends «augmentTypeRef»> augmentationType = augmentation.«DATA_CONTAINER_IMPLEMENTED_INTERFACE_NAME»();
405 if (!(this.«AUGMENTATION_FIELD» instanceof «hashMapRef»)) {
406 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>();
409 this.«AUGMENTATION_FIELD».put(augmentationType, augmentation);
414 * Remove an augmentation from this builder's product. If this builder does not track such an augmentation
415 * type, this method does nothing.
417 * @param augmentationType augmentation type to be removed
418 * @return this builder
420 public «type.name» removeAugmentation(«jlClassRef»<? extends «augmentTypeRef»> augmentationType) {
421 if (this.«AUGMENTATION_FIELD» instanceof «hashMapRef») {
422 this.«AUGMENTATION_FIELD».remove(augmentationType);
429 private def createDescription(GeneratedType targetType) {
430 val target = type.importedName
432 Class that builds {@link «target»} instances. Overall design of the class is that of a
433 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
436 In general, this class is supposed to be used like this template:
439 «target» createTarget(int fooXyzzy, int barBaz) {
440 return new «target»Builder()
441 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
442 .setBar(new BarBuilder().setBaz(barBaz).build())
449 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
450 worrying about synchronization issues.
453 As a side note: method chaining results in:
455 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
456 on the stack, so further method invocations just need to fill method arguments for the next method
457 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
458 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
460 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
461 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
462 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
467 @see «BUILDER.importedName»
471 override protected String formatDataForJavaDoc(GeneratedType type) {
472 val typeDescription = createDescription(type)
475 «IF !typeDescription.nullOrEmpty»
481 private def generateAugmentation() '''
482 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
483 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«CLASS.importedName»<E$$> augmentationType) {
484 return (E$$) «AUGMENTATION_FIELD».get(«JU_OBJECTS.importedName».requireNonNull(augmentationType));
488 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
489 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
490 «FOR field : keyProps»
491 this.«field.fieldName» = base.«field.getterMethodName»();
495 override protected CharSequence generateCopyNonKeys(Collection<BuilderGeneratedProperty> props) '''
497 this.«field.fieldName» = base.«field.getterName»();
501 override protected generateCopyAugmentation(Type implType) {
502 val hashMapRef = JU_HASHMAP.importedName
503 val augmentTypeRef = augmentType.importedName
505 «JU_MAP.importedName»<«CLASS.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug = base.augmentations();
506 if (!aug.isEmpty()) {
507 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
512 private static def hasNonDefaultMethods(GeneratedType type) {
513 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
516 private static def nonDefaultMethods(GeneratedType type) {
517 type.methodDefinitions.filter([def | !def.isDefault])