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.yangtools.sal.java.api.generator
10 import com.google.common.collect.ImmutableList
11 import com.google.common.collect.Lists
12 import com.google.common.collect.Range
13 import com.google.common.io.BaseEncoding
14 import java.beans.ConstructorProperties
15 import java.math.BigDecimal
16 import java.math.BigInteger
17 import java.util.ArrayList
18 import java.util.Arrays
19 import java.util.Collections
21 import java.util.regex.Pattern
22 import org.opendaylight.yangtools.binding.generator.util.TypeConstants
23 import org.opendaylight.yangtools.sal.binding.model.api.Constant
24 import org.opendaylight.yangtools.sal.binding.model.api.Enumeration
25 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty
26 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject
27 import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType
28 import org.opendaylight.yangtools.sal.binding.model.api.Restrictions
29 import org.opendaylight.yangtools.yang.model.api.type.BitsTypeDefinition
30 import com.google.common.base.Preconditions
31 import org.opendaylight.yangtools.sal.binding.model.api.Type
32 import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType
35 * Template for generating JAVA class.
37 class ClassTemplate extends BaseTemplate {
39 protected val List<GeneratedProperty> properties
40 protected val List<GeneratedProperty> finalProperties
41 protected val List<GeneratedProperty> parentProperties
42 protected val Iterable<GeneratedProperty> allProperties;
43 protected val Restrictions restrictions
46 * List of enumeration which are generated as JAVA enum type.
48 protected val List<Enumeration> enums
51 * List of constant instances which are generated as JAVA public static final attributes.
53 protected val List<Constant> consts
56 * List of generated types which are enclosed inside <code>genType</code>
58 protected val List<GeneratedType> enclosedGeneratedTypes;
60 protected val GeneratedTransferObject genTO;
62 private val AbstractRangeGenerator<?> rangeGenerator
65 * Creates instance of this class with concrete <code>genType</code>.
67 * @param genType generated transfer object which will be transformed to JAVA class source code
69 new(GeneratedTransferObject genType) {
72 this.properties = genType.properties
73 this.finalProperties = GeneratorUtil.resolveReadOnlyPropertiesFromTO(genTO.properties)
74 this.parentProperties = GeneratorUtil.getPropertiesOfAllParents(genTO)
75 this.restrictions = genType.restrictions
77 var List<GeneratedProperty> sorted = new ArrayList<GeneratedProperty>();
78 sorted.addAll(properties);
79 sorted.addAll(parentProperties);
80 Collections.sort(sorted, [p1, p2|
81 p1.name.compareTo(p2.name)
84 this.allProperties = sorted
85 this.enums = genType.enumerations
86 this.consts = genType.constantDefinitions
87 this.enclosedGeneratedTypes = genType.enclosedTypes
89 if (restrictions != null && !restrictions.rangeConstraints.nullOrEmpty) {
90 rangeGenerator = AbstractRangeGenerator.forType(findProperty(genType, "value").returnType)
91 Preconditions.checkNotNull(rangeGenerator)
98 * Generates JAVA class source code (class body only).
100 * @return string with JAVA class body source code
102 def CharSequence generateAsInnerClass() {
103 return generateBody(true)
106 override protected body() {
111 * Template method which generates class body.
113 * @param isInnerClass boolean value which specify if generated class is|isn't inner
114 * @return string with class source code in JAVA format
116 def protected generateBody(boolean isInnerClass) '''
117 «wrapToDocumentation(formatDataForJavaDoc(type))»
118 «generateClassDeclaration(isInnerClass)» {
120 «innerClassesDeclarations»
122 «constantsDeclarations»
125 «IF restrictions != null && (!restrictions.rangeConstraints.nullOrEmpty ||
126 !restrictions.lengthConstraints.nullOrEmpty)»
127 «generateConstraints»
129 «IF !restrictions.rangeConstraints.nullOrEmpty»
130 «rangeGenerator.generateRangeChecker("_value", restrictions.rangeConstraints)»
138 «FOR field : properties SEPARATOR "\n"»
145 «IF (genTO.isTypedef() && genTO.getBaseType instanceof BitsTypeDefinition)»
146 «generateGetValueForBitsTypeDef»
153 «generateToString(genTO.toStringIdentifiers)»
155 «generateLengthMethod("length", "_length")»
157 «generateRangeMethod("range", "_range")»
164 * Template method which generates the method <code>getValue()</code> for typedef,
165 * which base type is BitsDefinition.
167 * @return string with the <code>getValue()</code> method definition in JAVA format
169 def protected generateGetValueForBitsTypeDef() '''
171 public boolean[] getValue() {
172 return new boolean[]{
173 «FOR property: genTO.properties SEPARATOR ','»
180 def private generateLengthMethod(String methodName, String varName) '''
181 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
182 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
184 * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
187 public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() {
193 def private generateRangeMethod(String methodName, String varName) '''
194 «IF restrictions != null && !(restrictions.rangeConstraints.empty)»
195 «val returnType = allProperties.iterator.next.returnType»
197 * @deprecated This method is slated for removal in a future release. See BUG-1485 for details.
200 public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() {
207 * Template method which generates inner classes inside this interface.
209 * @return string with the source code for inner classes in JAVA format
211 def protected innerClassesDeclarations() '''
212 «IF !enclosedGeneratedTypes.empty»
213 «FOR innerClass : enclosedGeneratedTypes SEPARATOR "\n"»
214 «IF (innerClass instanceof GeneratedTransferObject)»
215 «val classTemplate = new ClassTemplate(innerClass)»
216 «classTemplate.generateAsInnerClass»
223 def protected constructors() '''
225 «genUnionConstructor»
227 «allValuesConstructor»
229 «IF !allProperties.empty»
232 «IF properties.empty && !parentProperties.empty »
237 def private generateConstraints() '''
239 «IF !restrictions.rangeConstraints.nullOrEmpty»
240 «generateRangeConstraints»
242 «IF !restrictions.lengthConstraints.nullOrEmpty»
243 «generateLengthConstraints»
248 private def generateRangeConstraints() '''
249 «IF !allProperties.nullOrEmpty»
250 «val returnType = allProperties.iterator.next.returnType»
251 «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)»
252 «rangeBody(restrictions, BigDecimal, genTO.importedName, "_range")»
254 «rangeBody(restrictions, BigInteger, genTO.importedName, "_range")»
259 private def rangeBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
260 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
261 «FOR r : restrictions.rangeConstraints»
262 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
264 «varName» = builder.build();
267 private def lengthBody(Restrictions restrictions, Class<? extends Number> numberClass, String className, String varName) '''
268 «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder();
269 «FOR r : restrictions.lengthConstraints»
270 builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»));
272 «varName» = builder.build();
275 private def generateLengthConstraints() '''
276 «IF restrictions != null && !(restrictions.lengthConstraints.empty)»
277 «val numberClass = restrictions.lengthConstraints.iterator.next.min.class»
278 «IF numberClass.equals(typeof(BigDecimal))»
279 «lengthBody(restrictions, numberClass, genTO.importedName, "_length")»
281 «lengthBody(restrictions, typeof(BigInteger), genTO.importedName, "_length")»
286 def protected allValuesConstructor() '''
287 «IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
288 @«ConstructorProperties.importedName»("value")
290 public «type.name»(«allProperties.asArgumentsDeclaration») {
291 «IF false == parentProperties.empty»
292 super(«parentProperties.asArguments»);
294 «FOR p : allProperties»
295 «generateRestrictions(type, p.fieldName.toString, p.returnType)»
299 * If we have patterns, we need to apply them to the value field. This is a sad
300 * consequence of how this code is structured.
302 IF genTO.typedef && !allProperties.empty && allProperties.size == 1 && allProperties.get(0).name.equals("value")»
304 «Preconditions.importedName».checkNotNull(_value, "Supplied value may not be null");
307 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME && c.value instanceof List<?>»
308 for (Pattern p : patterns) {
309 «Preconditions.importedName».checkArgument(p.matcher(_value).matches(), "Supplied value \"%s\" does not match any of the permitted patterns %s", _value, «TypeConstants.PATTERN_CONSTANT_NAME»);
317 «IF p.returnType.importedName.contains("[]")»
318 this.«p.fieldName» = «p.fieldName» == null ? null : «p.fieldName».clone();
320 this.«p.fieldName» = «p.fieldName»;
327 def protected genUnionConstructor() '''
328 «FOR p : allProperties»
329 «val List<GeneratedProperty> other = new ArrayList(properties)»
331 «genConstructor(p, other)»
337 def protected genConstructor(GeneratedProperty property, GeneratedProperty... other) '''
338 public «type.name»(«property.returnType.importedName + " " + property.name») {
339 «IF false == parentProperties.empty»
340 super(«parentProperties.asArguments»);
343 «generateRestrictions(type, property.fieldName.toString, property.returnType)»
345 this.«property.fieldName» = «property.name»;
347 this.«p.fieldName» = null;
352 def private generateRestrictions(Type type, String paramName, Type returnType) '''
353 «val restrictions = type.getRestrictions»
354 «IF restrictions !== null»
355 «val boolean isNestedType = !(returnType instanceof ConcreteType)»
356 «IF !restrictions.lengthConstraints.empty»
357 «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)»
359 «IF !restrictions.rangeConstraints.empty»
360 if («paramName» != null) {
362 «rangeGenerator.generateRangeCheckerCall(paramName, paramName + ".getValue()")»
364 «rangeGenerator.generateRangeCheckerCall(paramName, paramName)»
371 def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) '''
372 «val clazz = restrictions.lengthConstraints.iterator.next.min.class»
373 if («paramName» != null) {
374 «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))»
375 boolean isValidLength = false;
376 for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) {
377 if (r.contains(_constraint)) {
378 isValidLength = true;
381 if (!isValidLength) {
382 throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length()));
387 def protected copyConstructor() '''
389 * Creates a copy from Source Object.
391 * @param source Source object
393 public «type.name»(«type.name» source) {
394 «IF false == parentProperties.empty»
398 this.«p.fieldName» = source.«p.fieldName»;
403 def protected parentConstructor() '''
405 * Creates a new instance from «genTO.superType.importedName»
407 * @param source Source object
409 public «type.name»(«genTO.superType.importedName» source) {
414 def protected defaultInstance() '''
415 «IF genTO.typedef && !allProperties.empty && !genTO.unionType»
416 «val prop = allProperties.get(0)»
417 «IF !("org.opendaylight.yangtools.yang.binding.InstanceIdentifier".equals(prop.returnType.fullyQualifiedName))»
418 public static «genTO.name» getDefaultInstance(String defaultValue) {
419 «IF "byte[]".equals(prop.returnType.name)»
420 «BaseEncoding.importedName» baseEncoding = «BaseEncoding.importedName».base64();
421 return new «genTO.name»(baseEncoding.decode(defaultValue));
422 «ELSEIF "java.lang.String".equals(prop.returnType.fullyQualifiedName)»
423 return new «genTO.name»(defaultValue);
424 «ELSEIF allProperties.size > 1»
426 «ELSEIF "java.lang.Boolean".equals(prop.returnType.fullyQualifiedName)»
427 return new «genTO.name»(Boolean.valueOf(defaultValue));
428 «ELSEIF "java.lang.Byte".equals(prop.returnType.fullyQualifiedName)»
429 return new «genTO.name»(Byte.valueOf(defaultValue));
430 «ELSEIF "java.lang.Short".equals(prop.returnType.fullyQualifiedName)»
431 return new «genTO.name»(Short.valueOf(defaultValue));
432 «ELSEIF "java.lang.Integer".equals(prop.returnType.fullyQualifiedName)»
433 return new «genTO.name»(Integer.valueOf(defaultValue));
434 «ELSEIF "java.lang.Long".equals(prop.returnType.fullyQualifiedName)»
435 return new «genTO.name»(Long.valueOf(defaultValue));
437 return new «genTO.name»(new «prop.returnType.importedName»(defaultValue));
444 def protected bitsArgs() '''
445 «List.importedName»<«String.importedName»> properties = «Lists.importedName».newArrayList(«allProperties.propsAsArgs»);
446 if (!properties.contains(defaultValue)) {
447 throw new «IllegalArgumentException.importedName»("invalid default parameter");
450 return new «genTO.name»(
451 «FOR prop : allProperties SEPARATOR ","»
452 properties.get(i++).equals(defaultValue) ? «Boolean.importedName».TRUE : null
457 def protected propsAsArgs(Iterable<GeneratedProperty> properties) '''
458 «FOR prop : properties SEPARATOR ","»
464 * Template method which generates JAVA class declaration.
466 * @param isInnerClass boolean value which specify if generated class is|isn't inner
467 * @return string with class declaration in JAVA format
469 def protected generateClassDeclaration(boolean isInnerClass) '''
473 ELSEIF (type.abstract)»«
477 ENDIF»class «type.name»«
478 IF (genTO.superType != null)»«
479 " extends "»«genTO.superType.importedName»«
481 «IF (!type.implements.empty)»«
483 FOR type : type.implements SEPARATOR ", "»«
490 * Template method which generates JAVA enum type.
492 * @return string with inner enum source code in JAVA format
494 def protected enumDeclarations() '''
496 «FOR e : enums SEPARATOR "\n"»
497 «val enumTemplate = new EnumTemplate(e)»
498 «enumTemplate.generateAsInnerClass»
503 def protected suidDeclaration() '''
504 «IF genTO.SUID != null»
505 private static final long serialVersionUID = «genTO.SUID.value»L;
510 * Template method which generates JAVA constants.
512 * @return string with constants in JAVA format
514 def protected constantsDeclarations() '''
517 «IF c.name == TypeConstants.PATTERN_CONSTANT_NAME»
518 «val cValue = c.value»
519 «IF cValue instanceof List<?>»
520 private static final «Pattern.importedName»[] «Constants.MEMBER_PATTERN_LIST»;
521 public static final «List.importedName»<String> «TypeConstants.PATTERN_CONSTANT_NAME» = «ImmutableList.importedName».of(«
522 FOR v : cValue SEPARATOR ", "»«
523 IF v instanceof String»"«
528 «generateStaticInicializationBlock»
531 public static final «c.type.importedName» «c.name» = «c.value»;
538 * Template method which generates JAVA static initialization block.
540 * @return string with static initialization block in JAVA format
542 def protected generateStaticInicializationBlock() '''
544 final «Pattern.importedName» a[] = new «Pattern.importedName»[«TypeConstants.PATTERN_CONSTANT_NAME».size()];
546 for (String regEx : «TypeConstants.PATTERN_CONSTANT_NAME») {
547 a[i++] = Pattern.compile(regEx);
550 «Constants.MEMBER_PATTERN_LIST» = a;
555 * Template method which generates JAVA class attributes.
557 * @return string with the class attributes in JAVA format
559 def protected generateFields() '''
560 «IF restrictions != null»
561 «val prop = getPropByName("value")»
563 «IF !(restrictions.lengthConstraints.empty)»
564 private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _length;
566 «IF !(restrictions.rangeConstraints.empty)»
567 private static final «List.importedName»<«Range.importedName»<«prop.returnType.importedNumber»>> _range;
571 «IF !properties.empty»
573 private«IF f.readOnly» final«ENDIF» «f.returnType.importedName» «f.fieldName»;
579 * Template method which generates the method <code>hashCode()</code>.
581 * @return string with the <code>hashCode()</code> method definition in JAVA format
583 def protected generateHashCode() '''
584 «IF !genTO.hashCodeIdentifiers.empty»
586 public int hashCode() {
587 final int prime = 31;
589 «FOR property : genTO.hashCodeIdentifiers»
590 «IF property.returnType.name.contains("[")»
591 result = prime * result + ((«property.fieldName» == null) ? 0 : «Arrays.importedName».hashCode(«property.fieldName»));
593 result = prime * result + ((«property.fieldName» == null) ? 0 : «property.fieldName».hashCode());
602 * Template method which generates the method <code>equals()</code>.
604 * @return string with the <code>equals()</code> method definition in JAVA format
606 def protected generateEquals() '''
607 «IF !genTO.equalsIdentifiers.empty»
609 public boolean equals(java.lang.Object obj) {
616 if (getClass() != obj.getClass()) {
619 «type.name» other = («type.name») obj;
620 «FOR property : genTO.equalsIdentifiers»
621 «val fieldName = property.fieldName»
622 if («fieldName» == null) {
623 if (other.«fieldName» != null) {
626 «IF property.returnType.name.contains("[")»
627 } else if(!«Arrays.importedName».equals(«fieldName», other.«fieldName»)) {
629 } else if(!«fieldName».equals(other.«fieldName»)) {
639 def GeneratedProperty getPropByName(String name) {
640 for (GeneratedProperty prop : allProperties) {
641 if (prop.name.equals(name)) {