/* * Copyright (c) 2014 Cisco Systems, Inc. and others. All rights reserved. * * This program and the accompanying materials are made available under the * terms of the Eclipse Public License v1.0 which accompanies this distribution, * and is available at http://www.eclipse.org/legal/epl-v10.html */ package org.opendaylight.yangtools.sal.java.api.generator import com.google.common.collect.ImmutableList import com.google.common.collect.Range import java.math.BigDecimal import java.math.BigInteger import java.util.Arrays import java.util.Collection import java.util.HashMap import java.util.List import java.util.Map import java.util.StringTokenizer import org.opendaylight.yangtools.binding.generator.util.Types import org.opendaylight.yangtools.sal.binding.model.api.ConcreteType import org.opendaylight.yangtools.sal.binding.model.api.GeneratedProperty import org.opendaylight.yangtools.sal.binding.model.api.GeneratedTransferObject import org.opendaylight.yangtools.sal.binding.model.api.GeneratedType import org.opendaylight.yangtools.sal.binding.model.api.MethodSignature import org.opendaylight.yangtools.sal.binding.model.api.Restrictions import org.opendaylight.yangtools.sal.binding.model.api.Type import org.opendaylight.yangtools.yang.common.QName abstract class BaseTemplate { protected val GeneratedType type; protected val Map importMap; private static final String NEW_LINE = '\n' new(GeneratedType _type) { if (_type == null) { throw new IllegalArgumentException("Generated type reference cannot be NULL!") } this.type = _type; this.importMap = new HashMap() } def packageDefinition() '''package «type.packageName»;''' protected def getFullyQualifiedName() { return type.fullyQualifiedName } final public def generate() { val _body = body() ''' «packageDefinition» «imports» «_body» '''.toString } protected def imports() ''' «IF !importMap.empty» «FOR entry : importMap.entrySet» «IF entry.value != fullyQualifiedName» import «entry.value».«entry.key»; «ENDIF» «ENDFOR» «ENDIF» ''' protected abstract def CharSequence body(); // Helper patterns final protected def fieldName(GeneratedProperty property) '''_«property.name»''' final protected def propertyNameFromGetter(MethodSignature getter) { var int prefix; if (getter.name.startsWith("is")) { prefix = 2 } else if (getter.name.startsWith("get")) { prefix = 3 } else { throw new IllegalArgumentException("Not a getter") } return getter.name.substring(prefix).toFirstLower; } /** * Template method which generates the getter method for field * * @param field * generated property with data about field which is generated as the getter method * @return string with the getter method source code in JAVA format */ final protected def getterMethod(GeneratedProperty field) { ''' public «field.returnType.importedName» «field.getterMethodName»() { return «field.fieldName»; } ''' } final protected def getterMethodName(GeneratedProperty field) { val prefix = if(field.returnType.equals(Types.BOOLEAN)) "is" else "get" return '''«prefix»«field.name.toFirstUpper»''' } /** * Template method which generates the setter method for field * * @param field * generated property with data about field which is generated as the setter method * @return string with the setter method source code in JAVA format */ final protected def setterMethod(GeneratedProperty field) ''' «val returnType = field.returnType.importedName» public «type.name» set«field.name.toFirstUpper»(«returnType» value) { this.«field.fieldName» = value; return this; } ''' final protected def importedName(Type intype) { GeneratorUtil.putTypeIntoImports(type, intype, importMap); GeneratorUtil.getExplicitType(type, intype, importMap) } final protected def importedName(Class cls) { importedName(Types.typeForClass(cls)) } /** * Template method which generates method parameters with their types from parameters. * * @param parameters * group of generated property instances which are transformed to the method parameters * @return string with the list of the method parameters with their types in JAVA format */ def final protected asArgumentsDeclaration(Iterable parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter. returnType.importedName» «parameter.fieldName»«ENDFOR»«ENDIF»''' /** * Template method which generates sequence of the names of the class attributes from parameters. * * @param parameters * group of generated property instances which are transformed to the sequence of parameter names * @return string with the list of the parameter names of the parameters */ def final protected asArguments(Iterable parameters) '''«IF !parameters.empty»«FOR parameter : parameters SEPARATOR ", "»«parameter. fieldName»«ENDFOR»«ENDIF»''' /** * Template method which generates JAVA comments. * * @param comment string with the comment for whole JAVA class * @return string with comment in JAVA format */ def protected CharSequence asJavadoc(String comment) { if(comment == null) return '' var txt = comment if (txt.contains("*/")) { txt = txt.replace("*/", "*/") } txt = comment.trim txt = formatToParagraph(txt) return ''' «wrapToDocumentation(txt)» ''' } def String wrapToDocumentation(String text) { val StringTokenizer tokenizer = new StringTokenizer(text, "\n", false) val StringBuilder sb = new StringBuilder() if(text.empty) return "" sb.append("/**") sb.append(NEW_LINE) while(tokenizer.hasMoreTokens) { sb.append(" * ") sb.append(tokenizer.nextToken) sb.append(NEW_LINE) } sb.append(" */") return sb.toString } def protected String formatDataForJavaDoc(GeneratedType type) { val typeDescription = type.getDescription(); return ''' «IF !typeDescription.nullOrEmpty» «typeDescription» «ENDIF» '''.toString } def asLink(String text) { val StringBuilder sb = new StringBuilder() var tempText = text var char lastChar = ' ' var boolean badEnding = false if(text.endsWith(".") || text.endsWith(":") || text.endsWith(",")) { tempText = text.substring(0, text.length - 1) lastChar = text.charAt(text.length - 1) badEnding = true } sb.append("") sb.append(tempText) sb.append("") if(badEnding) sb.append(lastChar) return sb.toString } protected def formatToParagraph(String text) { if(text == null || text.isEmpty) return text var formattedText = text val StringBuilder sb = new StringBuilder(); var StringBuilder lineBuilder = new StringBuilder(); var boolean isFirstElementOnNewLineEmptyChar = false; formattedText = formattedText.replace("*/", "*/") formattedText = formattedText.replace(NEW_LINE, "") formattedText = formattedText.replace("\t", "") formattedText = formattedText.replaceAll(" +", " "); val StringTokenizer tokenizer = new StringTokenizer(formattedText, " ", true); while(tokenizer.hasMoreElements) { val nextElement = tokenizer.nextElement.toString if(lineBuilder.length + nextElement.length > 80) { if (lineBuilder.charAt(lineBuilder.length - 1) == ' ') { lineBuilder.setLength(0) lineBuilder.append(lineBuilder.substring(0, lineBuilder.length - 1)) } if (lineBuilder.charAt(0) == ' ') { lineBuilder.setLength(0) lineBuilder.append(lineBuilder.substring(1)) } sb.append(lineBuilder); lineBuilder.setLength(0) sb.append(NEW_LINE) if(nextElement.toString == ' ') isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar; } if(isFirstElementOnNewLineEmptyChar) { isFirstElementOnNewLineEmptyChar = !isFirstElementOnNewLineEmptyChar } else { lineBuilder.append(nextElement) } } sb.append(lineBuilder) sb.append(NEW_LINE) return sb.toString } def isDocumentationParametersNullOrEmtpy(GeneratedType type) { val boolean isTypeDescriptionNullOrEmpty = type.description.nullOrEmpty val boolean isTypeReferenceNullOrEmpty = type.reference.nullOrEmpty val boolean isTypeModuleNameNullOrEmpty = type.moduleName.nullOrEmpty val boolean isTypeSchemaPathNullOrEmpty = type.schemaPath.nullOrEmpty if (isTypeDescriptionNullOrEmpty && isTypeReferenceNullOrEmpty && isTypeModuleNameNullOrEmpty && isTypeSchemaPathNullOrEmpty) { return true } return false } def generateRestrictions(Type type, String paramName, Type returnType) ''' «val restrictions = type.getRestrictions» «IF restrictions !== null» «val boolean isNestedType = !(returnType instanceof ConcreteType)» «IF !restrictions.lengthConstraints.empty» «generateLengthRestriction(returnType, restrictions, paramName, isNestedType)» «ENDIF» «IF !restrictions.rangeConstraints.empty» «generateRangeRestriction(returnType, paramName, isNestedType)» «ENDIF» «ENDIF» ''' def private generateLengthRestriction(Type returnType, Restrictions restrictions, String paramName, boolean isNestedType) ''' «val clazz = restrictions.lengthConstraints.iterator.next.min.class» if («paramName» != null) { «printLengthConstraint(returnType, clazz, paramName, isNestedType, returnType.name.contains("["))» boolean isValidLength = false; for («Range.importedName»<«clazz.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»length()) { if (r.contains(_constraint)) { isValidLength = true; } } if (!isValidLength) { throw new IllegalArgumentException(String.format("Invalid length: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»length())); } } ''' def private generateRangeRestriction(Type returnType, String paramName, boolean isNestedType) ''' if («paramName» != null) { «printRangeConstraint(returnType, paramName, isNestedType)» boolean isValidRange = false; for («Range.importedName»<«returnType.importedNumber»> r : «IF isNestedType»«returnType.importedName».«ENDIF»range()) { if (r.contains(_constraint)) { isValidRange = true; } } if (!isValidRange) { throw new IllegalArgumentException(String.format("Invalid range: %s, expected: %s.", «paramName», «IF isNestedType»«returnType.importedName».«ENDIF»range())); } } ''' /** * Print length constraint. * This should always be a BigInteger (only string and binary can have length restriction) */ def printLengthConstraint(Type returnType, Class clazz, String paramName, boolean isNestedType, boolean isArray) ''' «clazz.importedNumber» _constraint = «clazz.importedNumber».valueOf(«paramName»«IF isNestedType».getValue()«ENDIF».length«IF !isArray»()«ENDIF»); ''' def printRangeConstraint(Type returnType, String paramName, boolean isNestedType) ''' «IF BigDecimal.canonicalName.equals(returnType.fullyQualifiedName)» «BigDecimal.importedName» _constraint = new «BigDecimal.importedName»(«paramName»«IF isNestedType».getValue()«ENDIF».toString()); «ELSE» «IF isNestedType» «val propReturnType = findProperty(returnType as GeneratedTransferObject, "value").returnType» «IF propReturnType.fullyQualifiedName.equals(BigInteger.canonicalName)» «BigInteger.importedName» _constraint = «paramName».getValue(); «ELSE» «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName».getValue()); «ENDIF» «ELSE» «IF returnType.fullyQualifiedName.equals(BigInteger.canonicalName)» «BigInteger.importedName» _constraint = «paramName»; «ELSE» «BigInteger.importedName» _constraint = «BigInteger.importedName».valueOf(«paramName»); «ENDIF» «ENDIF» «ENDIF» ''' def protected generateToString(Collection properties) ''' «IF !properties.empty» @Override public «String.importedName» toString() { «StringBuilder.importedName» builder = new «StringBuilder.importedName»(«type.importedName».class.getSimpleName()).append(" ["); boolean first = true; «FOR property : properties» if («property.fieldName» != null) { if (first) { first = false; } else { builder.append(", "); } builder.append("«property.fieldName»="); «IF property.returnType.name.contains("[")» builder.append(«Arrays.importedName».toString(«property.fieldName»)); «ELSE» builder.append(«property.fieldName»); «ENDIF» } «ENDFOR» return builder.append(']').toString(); } «ENDIF» ''' def getRestrictions(Type type) { var Restrictions restrictions = null if (type instanceof ConcreteType) { restrictions = (type as ConcreteType).restrictions } else if (type instanceof GeneratedTransferObject) { restrictions = (type as GeneratedTransferObject).restrictions } return restrictions } def boolean isArrayType(GeneratedTransferObject type) { var isArray = false val GeneratedProperty value = findProperty(type, "value") if (value != null && value.returnType.name.contains("[")) { isArray = true } return isArray } def String toQuote(Object obj) { return "\"" + obj.toString + "\""; } /** * Template method which generates method parameters with their types from parameters. * * @param parameters * list of parameter instances which are transformed to the method parameters * @return string with the list of the method parameters with their types in JAVA format */ def protected generateParameters(List parameters) '''« IF !parameters.empty»« FOR parameter : parameters SEPARATOR ", "»« parameter.type.importedName» «parameter.name»« ENDFOR»« ENDIF »''' def protected generateLengthMethod(String methodName, Type type, String className, String varName) ''' «val Restrictions restrictions = type.restrictions» «IF restrictions != null && !(restrictions.lengthConstraints.empty)» «val numberClass = restrictions.lengthConstraints.iterator.next.min.class» public static «List.importedName»<«Range.importedName»<«numberClass.importedNumber»>> «methodName»() { «IF numberClass.equals(typeof(BigDecimal))» «lengthBody(restrictions, numberClass, className, varName)» «ELSE» «lengthBody(restrictions, typeof(BigInteger), className, varName)» «ENDIF» } «ENDIF» ''' def private lengthBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' if («varName» == null) { synchronized («className».class) { if («varName» == null) { «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder(); «FOR r : restrictions.lengthConstraints» builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»)); «ENDFOR» «varName» = builder.build(); } } } return «varName»; ''' def protected generateRangeMethod(String methodName, Restrictions restrictions, Type returnType, String className, String varName) ''' «IF restrictions != null && !(restrictions.rangeConstraints.empty)» «val number = returnType.importedNumber» public static «List.importedName»<«Range.importedName»<«number»>> «methodName»() { «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)» «rangeBody(restrictions, BigDecimal, className, varName)» «ELSE» «rangeBody(restrictions, BigInteger, className, varName)» «ENDIF» } «ENDIF» ''' def protected generateRangeMethod(String methodName, Restrictions restrictions, String className, String varName, Iterable properties) ''' «IF restrictions != null && !(restrictions.rangeConstraints.empty)» «val returnType = properties.iterator.next.returnType» public static «List.importedName»<«Range.importedName»<«returnType.importedNumber»>> «methodName»() { «IF returnType.fullyQualifiedName.equals(BigDecimal.canonicalName)» «rangeBody(restrictions, BigDecimal, className, varName)» «ELSE» «rangeBody(restrictions, BigInteger, className, varName)» «ENDIF» } «ENDIF» ''' def private rangeBody(Restrictions restrictions, Class numberClass, String className, String varName) ''' if («varName» == null) { synchronized («className».class) { if («varName» == null) { «ImmutableList.importedName».Builder<«Range.importedName»<«numberClass.importedName»>> builder = «ImmutableList.importedName».builder(); «FOR r : restrictions.rangeConstraints» builder.add(«Range.importedName».closed(«numericValue(numberClass, r.min)», «numericValue(numberClass, r.max)»)); «ENDFOR» «varName» = builder.build(); } } } return «varName»; ''' def protected String importedNumber(Class clazz) { if (clazz.equals(typeof(BigDecimal))) { return BigDecimal.importedName } return BigInteger.importedName } def protected String importedNumber(Type clazz) { if (clazz.fullyQualifiedName.equals(BigDecimal.canonicalName)) { return BigDecimal.importedName } return BigInteger.importedName } def protected String numericValue(Class clazz, Object numberValue) { val number = clazz.importedName; val value = numberValue.toString if (clazz.equals(typeof(BigInteger)) || clazz.equals(typeof(BigDecimal))) { if (value.equals("0")) { return number + ".ZERO" } else if (value.equals("1")) { return number + ".ONE" } else if (value.equals("10")) { return number + ".TEN" } else { try { val Long longVal = Long.valueOf(value) return number + ".valueOf(" + longVal + "L)" } catch (NumberFormatException e) { if (clazz.equals(typeof(BigDecimal))) { try { val Double doubleVal = Double.valueOf(value); return number + ".valueOf(" + doubleVal + ")" } catch (NumberFormatException e2) { } } } } } return "new " + number + "(\"" + value + "\")" } def private GeneratedProperty findProperty(GeneratedTransferObject gto, String name) { val props = gto.properties for (prop : props) { if (prop.name.equals(name)) { return prop } } val GeneratedTransferObject parent = gto.superType if (parent != null) { return findProperty(parent, name) } return null } }