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 com.google.common.collect.ImmutableMap
16 import java.math.BigInteger
17 import java.util.ArrayList
18 import java.util.Collection
19 import java.util.HashMap
20 import java.util.HashSet
24 import java.util.regex.Pattern
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.JavaTypeName
29 import org.opendaylight.mdsal.binding.model.api.ParameterizedType
30 import org.opendaylight.mdsal.binding.model.api.Type
31 import org.opendaylight.mdsal.binding.model.util.TypeConstants
32 import org.opendaylight.mdsal.binding.model.util.Types
33 import org.opendaylight.mdsal.binding.spec.naming.BindingMapping
34 import org.opendaylight.yangtools.concepts.Builder
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.common.Uint8
39 import org.opendaylight.yangtools.yang.common.Uint16
40 import org.opendaylight.yangtools.yang.common.Uint64
41 import org.opendaylight.yangtools.yang.common.Uint32
44 * Template for generating JAVA builder classes.
46 class BuilderTemplate extends AbstractBuilderTemplate {
48 * Constant used as suffix for builder name.
50 public static val BUILDER = "Builder";
52 static val AUGMENTATION_FIELD_UPPER = AUGMENTATION_FIELD.toFirstUpper
54 static val UINT_TYPES = ImmutableMap.of(
55 Types.typeForClass(Uint8), Types.typeForClass(Short),
56 Types.typeForClass(Uint16), Types.typeForClass(Integer),
57 Types.typeForClass(Uint32), Types.typeForClass(Long),
58 Types.typeForClass(Uint64), Types.typeForClass(BigInteger)
62 * Constructs new instance of this class.
63 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
65 new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
67 super(genType, targetType, properties, augmentType, keyType)
70 override isLocalInnerClass(JavaTypeName name) {
71 // Builders do not have inner types
76 * Template method which generates JAVA class body for builder class and for IMPL class.
78 * @return string with JAVA source code
81 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
82 public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
84 «generateFields(false)»
86 «constantsDeclarations()»
88 «IF augmentType !== null»
89 «generateAugmentField()»
92 «generateConstructorsFromIfcs()»
94 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
96 «generateMethodFieldsFrom()»
98 «generateGetters(false)»
99 «IF augmentType !== null»
101 «generateAugmentation()»
106 @«Override.importedName»
107 public «targetType.name» build() {
108 return new «type.enclosedTypes.get(0).importedName»(this);
111 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
116 * Generate default constructor and constructor for every implemented interface from uses statements.
118 def private generateConstructorsFromIfcs() '''
119 public «type.name»() {
121 «IF (!(targetType instanceof GeneratedTransferObject))»
122 «FOR impl : targetType.implements»
123 «generateConstructorFromIfc(impl)»
129 * Generate constructor with argument of given type.
131 def private Object generateConstructorFromIfc(Type impl) '''
132 «IF (impl instanceof GeneratedType)»
133 «IF impl.hasNonDefaultMethods»
134 public «type.name»(«impl.fullyQualifiedName» arg) {
135 «printConstructorPropertySetter(impl)»
138 «FOR implTypeImplement : impl.implements»
139 «generateConstructorFromIfc(implTypeImplement)»
144 def private Object printConstructorPropertySetter(Type implementedIfc) '''
145 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
146 «val ifc = implementedIfc as GeneratedType»
147 «FOR getter : ifc.nonDefaultMethods»
148 «IF BindingMapping.isGetterMethodName(getter.name)»
149 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
152 «FOR impl : ifc.implements»
153 «printConstructorPropertySetter(impl)»
159 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
161 def private generateMethodFieldsFrom() '''
162 «IF (!(targetType instanceof GeneratedTransferObject))»
163 «IF targetType.hasImplementsFromUses»
164 «val List<Type> done = targetType.getBaseIfcs»
165 «generateMethodFieldsFromComment(targetType)»
166 public void fieldsFrom(«DataObject.importedName» arg) {
167 boolean isValidArg = false;
168 «FOR impl : targetType.getAllIfcs»
169 «generateIfCheck(impl, done)»
171 «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
177 def private generateMethodFieldsFromComment(GeneratedType type) '''
179 * Set fields from given grouping argument. Valid argument is instance of one of following types:
181 «FOR impl : type.getAllIfcs»
182 * <li>«impl.fullyQualifiedName»</li>
186 * @param arg grouping object
187 * @throws IllegalArgumentException if given argument is none of valid types
192 * Method is used to find out if given type implements any interface from uses.
194 def boolean hasImplementsFromUses(GeneratedType type) {
196 for (impl : type.getAllIfcs) {
197 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
204 def private generateIfCheck(Type impl, List<Type> done) '''
205 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
206 «val implType = impl as GeneratedType»
207 if (arg instanceof «implType.fullyQualifiedName») {
208 «printPropertySetter(implType)»
214 def private printPropertySetter(Type implementedIfc) '''
215 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
216 «val ifc = implementedIfc as GeneratedType»
217 «FOR getter : ifc.nonDefaultMethods»
218 «IF BindingMapping.isGetterMethodName(getter.name)»
219 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
225 private def List<Type> getBaseIfcs(GeneratedType type) {
226 val List<Type> baseIfcs = new ArrayList();
227 for (ifc : type.implements) {
228 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
235 private def Set<Type> getAllIfcs(Type type) {
236 val Set<Type> baseIfcs = new HashSet()
237 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
238 val ifc = type as GeneratedType
239 for (impl : ifc.implements) {
240 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
243 baseIfcs.addAll(impl.getAllIfcs)
249 private def List<String> toListOfNames(Collection<Type> types) {
250 val List<String> names = new ArrayList
252 names.add(type.fullyQualifiedName)
257 def private constantsDeclarations() '''
258 «FOR c : type.getConstantDefinitions»
259 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
260 «val cValue = c.value as Map<String, String>»
261 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
262 «IF cValue.size == 1»
263 private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«cValue.keySet.get(0).escapeJava»");
264 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«cValue.values.get(0).escapeJava»";
266 private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
267 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
268 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
269 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
277 def private generateListSetter(GeneratedProperty field, Type actualType) '''
278 «val restrictions = restrictionsForSetter(actualType)»
279 «IF restrictions !== null»
280 «generateCheckers(field, restrictions, actualType)»
282 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
283 «IF restrictions !== null»
284 if (values != null) {
285 for («actualType.getFullyQualifiedName» value : values) {
286 «checkArgument(field, restrictions, actualType, "value")»
290 this.«field.fieldName.toString» = values;
296 def private generateSetter(GeneratedProperty field, Type actualType) '''
297 «val restrictions = restrictionsForSetter(actualType)»
298 «IF restrictions !== null»
299 «generateCheckers(field, restrictions, actualType)»
302 «val setterName = "set" + field.getName.toFirstUpper»
303 public «type.getName» «setterName»(final «field.returnType.importedName» value) {
304 «IF restrictions !== null»
306 «checkArgument(field, restrictions, actualType, "value")»
309 this.«field.fieldName.toString» = value;
312 «val uintType = UINT_TYPES.get(field.returnType)»
313 «IF uintType !== null»
316 * Utility migration setter.
318 * @deprecated Use {#link «setterName»(«field.returnType.importedName»)} instead.
320 @Deprecated(forRemoval = true)
321 public «type.getName» «setterName»(final «uintType.importedName» value) {
322 return «setterName»(«CodeHelpers.importedName».compatUint(value));
327 private def Type getActualType(ParameterizedType ptype) {
328 return ptype.getActualTypeArguments.get(0)
332 * Template method which generates setter methods
334 * @return string with the setter methods
336 def private generateSetters() '''
337 «IF keyType !== null»
338 public «type.getName» withKey(final «keyType.importedName» key) {
343 «FOR property : properties»
344 «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
345 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
347 «generateSetter(property, property.returnType)»
351 «IF augmentType !== null»
352 «val augmentTypeRef = augmentType.importedName»
353 public «type.name» add«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
354 if (augmentationValue == null) {
355 return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
358 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
359 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
362 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
366 public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType) {
367 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
368 this.«AUGMENTATION_FIELD».remove(augmentationType);
375 private def createDescription(GeneratedType targetType) {
376 val target = type.importedName
378 Class that builds {@link «target»} instances. Overall design of the class is that of a
379 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
382 In general, this class is supposed to be used like this template:
385 «target» createTarget(int fooXyzzy, int barBaz) {
386 return new «target»Builder()
387 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
388 .setBar(new BarBuilder().setBaz(barBaz).build())
395 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
396 worrying about synchronization issues.
399 As a side note: method chaining results in:
401 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
402 on the stack, so further method invocations just need to fill method arguments for the next method
403 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
404 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
406 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
407 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
408 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
413 @see «Builder.importedName»
417 override protected String formatDataForJavaDoc(GeneratedType type) {
418 val typeDescription = createDescription(type)
421 «IF !typeDescription.nullOrEmpty»
427 private def generateAugmentation() '''
428 @«SuppressWarnings.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
429 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
430 return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
434 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
435 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
436 «FOR field : keyProps»
437 this.«field.fieldName» = base.«field.getterMethodName»();
441 override protected generateCopyAugmentation(Type implType) {
442 val augmentationHolderRef = AugmentationHolder.importedName
443 val typeRef = targetType.importedName
444 val hashMapRef = HashMap.importedName
445 val augmentTypeRef = augmentType.importedName
447 if (base instanceof «augmentationHolderRef») {
448 @SuppressWarnings("unchecked")
449 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
450 if (!aug.isEmpty()) {
451 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
457 private static def hasNonDefaultMethods(GeneratedType type) {
458 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
461 private static def nonDefaultMethods(GeneratedType type) {
462 type.methodDefinitions.filter([def | !def.isDefault])