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
48 static val SUPPRESS_WARNINGS = JavaTypeName.create(SuppressWarnings)
51 * Constructs new instance of this class.
52 * @throws IllegalArgumentException if <code>genType</code> equals <code>null</code>
54 new(GeneratedType genType, GeneratedType targetType, Set<GeneratedProperty> properties, Type augmentType,
56 super(genType, targetType, properties, augmentType, keyType)
59 override isLocalInnerClass(JavaTypeName name) {
60 // Builders do not have inner types
65 * Template method which generates JAVA class body for builder class and for IMPL class.
67 * @return string with JAVA source code
70 «wrapToDocumentation(formatDataForJavaDoc(targetType))»
71 «targetType.annotations.generateDeprecatedAnnotation»
72 public class «type.name» implements «Builder.importedName»<«targetType.importedName»> {
74 «generateFields(false)»
76 «constantsDeclarations()»
78 «IF augmentType !== null»
79 «generateAugmentField()»
82 «generateConstructorsFromIfcs()»
84 public «generateCopyConstructor(targetType, type.enclosedTypes.get(0))»
86 «generateMethodFieldsFrom()»
88 «generateGetters(false)»
89 «IF augmentType !== null»
91 «generateAugmentation()»
96 @«Override.importedName»
97 public «targetType.name» build() {
98 return new «type.enclosedTypes.get(0).importedName»(this);
101 «new BuilderImplTemplate(this, type.enclosedTypes.get(0)).body»
105 override generateDeprecatedAnnotation(AnnotationType ann) {
106 val forRemoval = ann.getParameter("forRemoval")
107 if (forRemoval !== null) {
108 return "@" + DEPRECATED.importedName + "(forRemoval = " + forRemoval.value + ")"
110 return "@" + SUPPRESS_WARNINGS.importedName + "(\"deprecation\")"
114 * Generate default constructor and constructor for every implemented interface from uses statements.
116 def private generateConstructorsFromIfcs() '''
117 public «type.name»() {
119 «IF (!(targetType instanceof GeneratedTransferObject))»
120 «FOR impl : targetType.implements»
121 «generateConstructorFromIfc(impl)»
127 * Generate constructor with argument of given type.
129 def private Object generateConstructorFromIfc(Type impl) '''
130 «IF (impl instanceof GeneratedType)»
131 «IF impl.hasNonDefaultMethods»
132 public «type.name»(«impl.fullyQualifiedName» arg) {
133 «printConstructorPropertySetter(impl)»
136 «FOR implTypeImplement : impl.implements»
137 «generateConstructorFromIfc(implTypeImplement)»
142 def private Object printConstructorPropertySetter(Type implementedIfc) '''
143 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
144 «val ifc = implementedIfc as GeneratedType»
145 «FOR getter : ifc.nonDefaultMethods»
146 «IF BindingMapping.isGetterMethodName(getter.name)»
147 this._«getter.propertyNameFromGetter» = arg.«getter.name»();
150 «FOR impl : ifc.implements»
151 «printConstructorPropertySetter(impl)»
157 * Generate 'fieldsFrom' method to set builder properties based on type of given argument.
159 def private generateMethodFieldsFrom() '''
160 «IF (!(targetType instanceof GeneratedTransferObject))»
161 «IF targetType.hasImplementsFromUses»
162 «val List<Type> done = targetType.getBaseIfcs»
163 «generateMethodFieldsFromComment(targetType)»
164 public void fieldsFrom(«DataObject.importedName» arg) {
165 boolean isValidArg = false;
166 «FOR impl : targetType.getAllIfcs»
167 «generateIfCheck(impl, done)»
169 «CodeHelpers.importedName».validValue(isValidArg, arg, "«targetType.getAllIfcs.toListOfNames»");
175 def private generateMethodFieldsFromComment(GeneratedType type) '''
177 * Set fields from given grouping argument. Valid argument is instance of one of following types:
179 «FOR impl : type.getAllIfcs»
180 * <li>«impl.fullyQualifiedName»</li>
184 * @param arg grouping object
185 * @throws IllegalArgumentException if given argument is none of valid types
190 * Method is used to find out if given type implements any interface from uses.
192 def boolean hasImplementsFromUses(GeneratedType type) {
194 for (impl : type.getAllIfcs) {
195 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
202 def private generateIfCheck(Type impl, List<Type> done) '''
203 «IF (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods)»
204 «val implType = impl as GeneratedType»
205 if (arg instanceof «implType.fullyQualifiedName») {
206 «printPropertySetter(implType)»
212 def private printPropertySetter(Type implementedIfc) '''
213 «IF (implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))»
214 «val ifc = implementedIfc as GeneratedType»
215 «FOR getter : ifc.nonDefaultMethods»
216 «IF BindingMapping.isGetterMethodName(getter.name)»
217 this._«getter.propertyNameFromGetter» = ((«implementedIfc.fullyQualifiedName»)arg).«getter.name»();
223 private def List<Type> getBaseIfcs(GeneratedType type) {
224 val List<Type> baseIfcs = new ArrayList();
225 for (ifc : type.implements) {
226 if (ifc instanceof GeneratedType && (ifc as GeneratedType).hasNonDefaultMethods) {
233 private def Set<Type> getAllIfcs(Type type) {
234 val Set<Type> baseIfcs = new HashSet()
235 if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) {
236 val ifc = type as GeneratedType
237 for (impl : ifc.implements) {
238 if (impl instanceof GeneratedType && (impl as GeneratedType).hasNonDefaultMethods) {
241 baseIfcs.addAll(impl.getAllIfcs)
247 private def List<String> toListOfNames(Collection<Type> types) {
248 val List<String> names = new ArrayList
250 names.add(type.fullyQualifiedName)
255 def private constantsDeclarations() '''
256 «FOR c : type.getConstantDefinitions»
257 «IF c.getName.startsWith(TypeConstants.PATTERN_CONSTANT_NAME)»
258 «val cValue = c.value as Map<String, String>»
259 «val String fieldSuffix = c.getName.substring(TypeConstants.PATTERN_CONSTANT_NAME.length)»
260 «IF cValue.size == 1»
261 «val firstEntry = cValue.entrySet.iterator.next»
262 private static final «Pattern.importedName» «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «Pattern.importedName».compile("«firstEntry.key.escapeJava»");
263 private static final String «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = "«firstEntry.value.escapeJava»";
265 private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»«fieldSuffix» = «CodeHelpers.importedName».compilePatterns(«ImmutableList.importedName».of(
266 «FOR v : cValue.keySet SEPARATOR ", "»"«v.escapeJava»"«ENDFOR»));
267 private static final String[] «Constants.MEMBER_REGEX_LIST»«fieldSuffix» = { «
268 FOR v : cValue.values SEPARATOR ", "»"«v.escapeJava»"«ENDFOR» };
276 def private generateListSetter(GeneratedProperty field, Type actualType) '''
277 «val restrictions = restrictionsForSetter(actualType)»
278 «IF restrictions !== null»
279 «generateCheckers(field, restrictions, actualType)»
281 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» values) {
282 «IF restrictions !== null»
283 if (values != null) {
284 for («actualType.getFullyQualifiedName» value : values) {
285 «checkArgument(field, restrictions, actualType, "value")»
289 this.«field.fieldName.toString» = values;
295 def private generateSetter(GeneratedProperty field, Type actualType) '''
296 «val restrictions = restrictionsForSetter(actualType)»
297 «IF restrictions !== null»
298 «generateCheckers(field, restrictions, actualType)»
301 public «type.getName» set«field.getName.toFirstUpper»(final «field.returnType.importedName» value) {
302 «IF restrictions !== null»
304 «checkArgument(field, restrictions, actualType, "value")»
307 this.«field.fieldName.toString» = value;
312 private def Type getActualType(ParameterizedType ptype) {
313 return ptype.getActualTypeArguments.get(0)
317 * Template method which generates setter methods
319 * @return string with the setter methods
321 def private generateSetters() '''
322 «IF keyType !== null»
323 public «type.getName» withKey(final «keyType.importedName» key) {
328 «FOR property : properties»
329 «IF property.returnType instanceof ParameterizedType && Types.isListType(property.returnType)»
330 «generateListSetter(property, getActualType(property.returnType as ParameterizedType))»
332 «generateSetter(property, property.returnType)»
336 «IF augmentType !== null»
337 «val augmentTypeRef = augmentType.importedName»
338 public «type.name» add«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType, «augmentTypeRef» augmentationValue) {
339 if (augmentationValue == null) {
340 return remove«AUGMENTATION_FIELD_UPPER»(augmentationType);
343 if (!(this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName»)) {
344 this.«AUGMENTATION_FIELD» = new «HashMap.importedName»<>();
347 this.«AUGMENTATION_FIELD».put(augmentationType, augmentationValue);
351 public «type.name» remove«AUGMENTATION_FIELD_UPPER»(«Class.importedName»<? extends «augmentTypeRef»> augmentationType) {
352 if (this.«AUGMENTATION_FIELD» instanceof «HashMap.importedName») {
353 this.«AUGMENTATION_FIELD».remove(augmentationType);
360 private def createDescription(GeneratedType targetType) {
361 val target = type.importedName
363 Class that builds {@link «target»} instances. Overall design of the class is that of a
364 <a href="https://en.wikipedia.org/wiki/Fluent_interface">fluent interface</a>, where method chaining is used.
367 In general, this class is supposed to be used like this template:
370 «target» createTarget(int fooXyzzy, int barBaz) {
371 return new «target»Builder()
372 .setFoo(new FooBuilder().setXyzzy(fooXyzzy).build())
373 .setBar(new BarBuilder().setBaz(barBaz).build())
380 This pattern is supported by the immutable nature of «target», as instances can be freely passed around without
381 worrying about synchronization issues.
384 As a side note: method chaining results in:
386 <li>very efficient Java bytecode, as the method invocation result, in this case the Builder reference, is
387 on the stack, so further method invocations just need to fill method arguments for the next method
388 invocation, which is terminated by {@link #build()}, which is then returned from the method</li>
389 <li>better understanding by humans, as the scope of mutable state (the builder) is kept to a minimum and is
391 <li>better optimization oportunities, as the object scope is minimized in terms of invocation (rather than
392 method) stack, making <a href="https://en.wikipedia.org/wiki/Escape_analysis">escape analysis</a> a lot
393 easier. Given enough compiler (JIT/AOT) prowess, the cost of th builder object can be completely
398 @see «Builder.importedName»
402 override protected String formatDataForJavaDoc(GeneratedType type) {
403 val typeDescription = createDescription(type)
406 «IF !typeDescription.nullOrEmpty»
412 private def generateAugmentation() '''
413 @«SUPPRESS_WARNINGS.importedName»({ "unchecked", "checkstyle:methodTypeParameterName"})
414 public <E$$ extends «augmentType.importedName»> E$$ «AUGMENTABLE_AUGMENTATION_NAME»(«Class.importedName»<E$$> augmentationType) {
415 return (E$$) «AUGMENTATION_FIELD».get(«CodeHelpers.importedName».nonNullValue(augmentationType, "augmentationType"));
419 override protected generateCopyKeys(List<GeneratedProperty> keyProps) '''
420 this.key = base.«BindingMapping.IDENTIFIABLE_KEY_NAME»();
421 «FOR field : keyProps»
422 this.«field.fieldName» = base.«field.getterMethodName»();
426 override protected generateCopyAugmentation(Type implType) {
427 val augmentationHolderRef = AugmentationHolder.importedName
428 val typeRef = targetType.importedName
429 val hashMapRef = HashMap.importedName
430 val augmentTypeRef = augmentType.importedName
432 if (base instanceof «augmentationHolderRef») {
433 @SuppressWarnings("unchecked")
434 «Map.importedName»<«Class.importedName»<? extends «augmentTypeRef»>, «augmentTypeRef»> aug =((«augmentationHolderRef»<«typeRef»>) base).augmentations();
435 if (!aug.isEmpty()) {
436 this.«AUGMENTATION_FIELD» = new «hashMapRef»<>(aug);
442 private static def hasNonDefaultMethods(GeneratedType type) {
443 !type.methodDefinitions.isEmpty && type.methodDefinitions.exists([def | !def.isDefault])
446 private static def nonDefaultMethods(GeneratedType type) {
447 type.methodDefinitions.filter([def | !def.isDefault])