From 82d16e9b6ada116bfcf1e6920aaf7d23e6b0ffe5 Mon Sep 17 00:00:00 2001 From: msunal Date: Thu, 25 Jul 2013 13:37:39 +0200 Subject: [PATCH] Created Yang to Java Builder pattern class generator - added Xtend tool to dependencies - created builder xtend class template - created BuilderClassDescriptor class which is used as source of information in template - integrated generating of Builder pattern classes for interfaces - updated jUnit test Maven generates .java file as a translation of .xtend template into src/main/xtend-gen. Therefore /binding-java-api-generator/src/main/xtend-gen was added into .gitignore file. If you use Xtend plugin for Eclipse, you should check a configuration of Xtend compiler in Eclipse, whether there is choosen xtend-gen as an output direcotory. Change-Id: I72e733c7d8f2587848b0e0d32368f7f1700a44d0 Signed-off-by: Martin Sunal --- .../yang-prototype/code-generator/.gitignore | 1 + .../binding-java-api-generator/pom.xml | 38 ++ .../api/generator/BuilderClassDescriptor.java | 331 ++++++++++++++++++ .../java/api/generator/BuilderGenerator.java | 26 ++ .../java/api/generator/BuilderTemplate.xtend | 96 +++++ .../sal/java/api/generator/Constants.java | 2 + .../java/api/generator/GeneratorJavaFile.java | 18 +- .../generator/test/GeneratorJavaFileTest.java | 5 +- 8 files changed, 511 insertions(+), 6 deletions(-) create mode 100644 opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderClassDescriptor.java create mode 100644 opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderGenerator.java create mode 100644 opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderTemplate.xtend diff --git a/opendaylight/sal/yang-prototype/code-generator/.gitignore b/opendaylight/sal/yang-prototype/code-generator/.gitignore index 40a85ffb28..acda8836ce 100644 --- a/opendaylight/sal/yang-prototype/code-generator/.gitignore +++ b/opendaylight/sal/yang-prototype/code-generator/.gitignore @@ -1 +1,2 @@ /.settings +/binding-java-api-generator/src/main/xtend-gen diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/pom.xml b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/pom.xml index fad725b1f8..86c8a8ad01 100644 --- a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/pom.xml +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/pom.xml @@ -7,6 +7,39 @@ 0.5.4-SNAPSHOT binding-java-api-generator + + + + org.eclipse.xtend + xtend-maven-plugin + 2.4.2 + + + + compile + + + ${basedir}/src/main/xtend-gen + + + + + + maven-clean-plugin + 2.4.1 + + + + ${basedir}/src/main/xtend-gen + + ** + + + + + + + org.opendaylight.controller @@ -25,5 +58,10 @@ junit junit + + org.eclipse.xtend + org.eclipse.xtend.lib + 2.4.2 + diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderClassDescriptor.java b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderClassDescriptor.java new file mode 100644 index 0000000000..72b4c87bf6 --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderClassDescriptor.java @@ -0,0 +1,331 @@ +package org.opendaylight.controller.sal.java.api.generator; + +import java.lang.reflect.Method; +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.LinkedHashSet; +import java.util.List; +import java.util.Map; +import java.util.Set; + +import org.opendaylight.controller.sal.binding.model.api.GeneratedTransferObject; +import org.opendaylight.controller.sal.binding.model.api.GeneratedType; +import org.opendaylight.controller.sal.binding.model.api.MethodSignature; +import org.opendaylight.controller.sal.binding.model.api.ParameterizedType; +import org.opendaylight.controller.sal.binding.model.api.Type; +import org.opendaylight.controller.yang.binding.Augmentable; + +public class BuilderClassDescriptor { + + private static final String GET_PREFIX = "get"; + private static final String JAVA_UTIL = "java.util"; + private static final String HASH_MAP = "HashMap"; + private static final String MAP = "Map"; + private static final String GET_AUGMENTATION_METHOD_NAME = "getAugmentation"; + + private final GeneratedType genType; + private Map imports; + private final String packageName; + private final String className; + private final Set methods; + private final Set fields; + private final List importsNames; + private FieldDeclaration augmentField; + + class TypeDeclaration { + + private final static String JAVA_LANG_PREFIX = "java.lang"; + private final String name; + private final TypeDeclaration[] generics; + + public TypeDeclaration(String pkg, String name, TypeDeclaration... generics) { + this.name = removeJavaLangPkgName(getRightTypeName(pkg, name)); + if (generics != null && generics.length > 0) { + this.generics = generics; + } else { + this.generics = null; + } + } + + public TypeDeclaration(final Type type) { + if (type == null) { + throw new IllegalArgumentException("Type cannot be NULL"); + } + + this.name = removeJavaLangPkgName(getRightTypeName(type.getPackageName(), type.getName())); + TypeDeclaration[] generics = null; + if (type instanceof ParameterizedType) { + final ParameterizedType pType = (ParameterizedType) type; + final Type[] actualTypeArguments = pType.getActualTypeArguments(); + generics = new TypeDeclaration[actualTypeArguments.length]; + for (int i = 0; i < actualTypeArguments.length; i++) { + generics[i] = new TypeDeclaration(actualTypeArguments[i].getPackageName(), + actualTypeArguments[i].getName()); + } + } + if (generics != null && generics.length > 0) { + this.generics = generics; + } else { + this.generics = null; + } + } + + private String removeJavaLangPkgName(final String typeName) { + if (typeName.startsWith(JAVA_LANG_PREFIX)) { + return typeName.substring(typeName.lastIndexOf(Constants.DOT) + 1); + } + return typeName; + } + + private String getRightTypeName(final String pkg, final String name) { + if (name == null) { + throw new IllegalArgumentException("Name cannot be NULL!"); + } + + if (imports == null) { + return name; + } + final String pkgFromImports = imports.get(name); + if (pkgFromImports == null || pkgFromImports.equals(pkg)) { + return name; + } + return (pkg == null ? "" : pkg) + Constants.DOT + name; + } + + public String getName() { + return name; + } + + public TypeDeclaration[] getGenerics() { + return generics; + } + + } + + class ParameterDeclaration { + + private final TypeDeclaration type; + private final String name; + + public ParameterDeclaration(TypeDeclaration type, String name) { + this.type = type; + this.name = name; + } + + public TypeDeclaration getType() { + return type; + } + + public String getName() { + return name; + } + + } + + class MethodDeclaration { + + private final TypeDeclaration returnType; + private final String name; + private final List parameters; + + public MethodDeclaration(final TypeDeclaration returnType, final String name, + final List parameters) { + this.returnType = returnType; + this.name = name; + if (parameters != null && !parameters.isEmpty()) { + this.parameters = parameters; + } else { + this.parameters = null; + } + } + + public TypeDeclaration getReturnType() { + return returnType; + } + + public String getName() { + return name; + } + + public List getParameters() { + return parameters; + } + + } + + class FieldDeclaration extends ParameterDeclaration { + + public FieldDeclaration(final TypeDeclaration type, final String name) { + super(type, name); + } + + } + + public BuilderClassDescriptor(final GeneratedType genType) { + if (genType == null) { + throw new IllegalArgumentException("Generated type reference cannot be NULL!"); + } + this.genType = genType; + this.imports = GeneratorUtil.createImports(genType); + addToImports(genType.getPackageName(), genType.getName()); + packageName = genType.getPackageName(); + className = genType.getName(); + methods = createMethods(); + fields = createFieldsFromMethods(); + importsNames = createImportsNames(); + } + + private Set createMethods() { + final Set methods = new LinkedHashSet<>(); + storeMethodsOfIfc(methods, genType); + storeMethodsOfImplementedIfcs(methods, genType.getImplements()); + return methods; + } + + private void storeMethodsOfIfc(final Set methodStorage, final GeneratedType ifc) { + for (MethodSignature methodSignature : ifc.getMethodDefinitions()) { + final List parameterDeclarations = getParameterDeclarationsFrom(methodSignature + .getParameters()); + methodStorage.add(new MethodDeclaration(new TypeDeclaration(methodSignature.getReturnType()), + methodSignature.getName(), parameterDeclarations)); + } + if (ifc.getEnclosedTypes() != null && !ifc.getEnclosedTypes().isEmpty()) { + addToImports(ifc.getPackageName(), ifc.getName() + ".*"); + } + } + + private List getParameterDeclarationsFrom(final List parameters) { + final List parameterDeclarations = new ArrayList<>(); + for (MethodSignature.Parameter mp : parameters) { + parameterDeclarations.add(new ParameterDeclaration(new TypeDeclaration(mp.getType()), mp.getName())); + } + return parameterDeclarations; + } + + private void storeMethodsOfImplementedIfcs(final Set methodStorage, + final List implementedIfcs) { + if (implementedIfcs == null || implementedIfcs.isEmpty()) { + return; + } + for (Type implementedIfc : implementedIfcs) { + if ((implementedIfc instanceof GeneratedType && !(implementedIfc instanceof GeneratedTransferObject))) { + final GeneratedType ifc = ((GeneratedType) implementedIfc); + storeMethodsOfIfc(methodStorage, ifc); + storeMethodsOfImplementedIfcs(methodStorage, ifc.getImplements()); + } else if (implementedIfc.getFullyQualifiedName().equals(Augmentable.class.getName())) { + for (Method m : Augmentable.class.getMethods()) { + if (m.getName().equals(GET_AUGMENTATION_METHOD_NAME)) { + addToImports(JAVA_UTIL, HASH_MAP); + addToImports(JAVA_UTIL, MAP); + java.lang.reflect.Type returnType = m.getReturnType(); + final String fullyQualifiedName = ((Class) returnType).getName(); + addToImports(getPackageFrom(fullyQualifiedName), getNameFrom(fullyQualifiedName)); + TypeDeclaration augmentMethodType = new TypeDeclaration(getPackageFrom(fullyQualifiedName), + getNameFrom(fullyQualifiedName), new TypeDeclaration(genType)); + augmentField = createFieldFromGetMethod(new MethodDeclaration(augmentMethodType, m.getName(), + null)); + } + } + } + } + } + + private void addToImports(final String pkg, final String name) { + if (imports == null) { + imports = new LinkedHashMap<>(); + } + if (imports.get(name) == null) { + imports.put(name, pkg); + } + } + + private String getPackageFrom(final String fullyQualifiedName) { + final int lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT); + return lastDotIndex == -1 ? "" : fullyQualifiedName.substring(0, lastDotIndex); + } + + private String getNameFrom(final String fullyQualifiedName) { + final int lastDotIndex = fullyQualifiedName.lastIndexOf(Constants.DOT); + return lastDotIndex == -1 ? fullyQualifiedName : fullyQualifiedName.substring(lastDotIndex + 1); + } + + private Set createFieldsFromMethods() { + final Set result = new LinkedHashSet<>(); + + if (methods == null || methods.isEmpty()) { + return result; + } + + for (MethodDeclaration m : methods) { + final FieldDeclaration createdField = createFieldFromGetMethod(m); + if (createdField != null) { + result.add(createdField); + } + } + return result; + } + + private FieldDeclaration createFieldFromGetMethod(final MethodDeclaration method) { + if (method == null || method.getName() == null || method.getName().isEmpty()) { + return null; + } else if (method.getName().startsWith(GET_PREFIX)) { + final String fieldNameFromMethod = method.getName().substring(GET_PREFIX.length()); + final String fieldName = Character.toLowerCase(fieldNameFromMethod.charAt(0)) + + fieldNameFromMethod.substring(1); + return new FieldDeclaration(method.getReturnType(), fieldName); + } + return null; + } + + private List createImportsNames() { + final List result = new ArrayList<>(); + + if (imports == null || imports.isEmpty()) { + return result; + } + + for (Map.Entry entry : imports.entrySet()) { + final String typeName = entry.getKey(); + final String packageName = entry.getValue(); + result.add(packageName + Constants.DOT + typeName); + } + return result; + } + + public String getPackageName() { + return packageName; + } + + /** + * @return list of imports or empty list + */ + public List getImportsNames() { + return importsNames; + } + + public String getClassName() { + return className; + } + + /** + * @return set of methods or empty set + */ + public Set getFields() { + return fields; + } + + /** + * @return set of methods or empty set + */ + public Set getMethods() { + return methods; + } + + /** + * @return declaration of augment field or NULL + */ + public FieldDeclaration getAugmentField() { + return augmentField; + } + +} diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderGenerator.java b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderGenerator.java new file mode 100644 index 0000000000..07ee733253 --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderGenerator.java @@ -0,0 +1,26 @@ +package org.opendaylight.controller.sal.java.api.generator; + +import java.io.IOException; +import java.io.StringWriter; +import java.io.Writer; + +import org.opendaylight.controller.sal.binding.model.api.CodeGenerator; +import org.opendaylight.controller.sal.binding.model.api.GeneratedTransferObject; +import org.opendaylight.controller.sal.binding.model.api.GeneratedType; +import org.opendaylight.controller.sal.binding.model.api.Type; + +public final class BuilderGenerator implements CodeGenerator { + + public static final String FILE_NAME_SUFFIX = "Builder"; + + @Override + public Writer generate(Type type) throws IOException { + Writer writer = new StringWriter(); + if (type instanceof GeneratedType && !(type instanceof GeneratedTransferObject)) { + BuilderTemplate builerGeneratorXtend = new BuilderTemplate(); + writer.write(builerGeneratorXtend.generate(new BuilderClassDescriptor((GeneratedType) type)).toString()); + } + return writer; + } + +} diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderTemplate.xtend b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderTemplate.xtend new file mode 100644 index 0000000000..484fbfb3db --- /dev/null +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/BuilderTemplate.xtend @@ -0,0 +1,96 @@ +package org.opendaylight.controller.sal.java.api.generator + +import java.util.List +import java.util.Set + +class BuilderTemplate { + + val static BUILDER = 'Builder' + val static IMPL = 'Impl' + + def generate(BuilderClassDescriptor cd) ''' + package «cd.packageName»; + «IF !cd.importsNames.empty» + + «FOR in : cd.importsNames» + import «in»; + «ENDFOR» + «ENDIF» + + public class «cd.className»«BUILDER» { + + «fields(cd.fields, cd.augmentField)» + + «IF !cd.fields.empty» + «FOR field : cd.fields SEPARATOR '\n'» + public «cd.className»«BUILDER» set«field.name.toFirstUpper»(«field.type.name»«field.type.generics.print» «field.name») { + this.«field.name» = «field.name»; + return this; + } + «ENDFOR» + «ENDIF» + «IF cd.augmentField != null» + + public «cd.className»«BUILDER» add«cd.augmentField.name.toFirstUpper»(Class augmentationType, «cd.augmentField.type.name»«cd.augmentField.type.generics.print» augmentation) { + this.«cd.augmentField.name».put(augmentationType, augmentation); + return this; + } + «ENDIF» + + public «cd.className» build() { + return new «cd.className»«IMPL»(); + } + + private class «cd.className»«IMPL» implements «cd.className» { + + «fields(cd.fields, cd.augmentField)» + + private «cd.className»«IMPL»() { + «IF !cd.fields.empty» + «FOR field : cd.fields» + this.«field.name» = «cd.className»«BUILDER».this.«field.name»; + «ENDFOR» + «ENDIF» + «IF cd.augmentField != null» + this.«cd.augmentField.name».putAll(«cd.className»«BUILDER».this.«cd.augmentField.name»); + «ENDIF» + } + + «IF !cd.fields.empty» + «FOR field : cd.fields SEPARATOR '\n'» + @Override + public «field.type.name»«field.type.generics.print» get«field.name.toFirstUpper»() { + return «field.name»; + } + «ENDFOR» + «ENDIF» + «IF cd.augmentField != null» + + @Override + public E get«cd.augmentField.name.toFirstUpper»(Class augmentationType) { + if (augmentationType == null) { + throw new IllegalArgumentException("Augmentation Type reference cannot be NULL!"); + } + return (E) «cd.augmentField.name».get(augmentationType); + } + «ENDIF» + + } + + } + ''' + + def private fields(Set fields, BuilderClassDescriptor.FieldDeclaration augmentField) ''' + «IF !fields.empty» + «FOR field : fields» + private «field.type.name»«field.type.generics.print» «field.name»; + «ENDFOR» + «ENDIF» + «IF augmentField != null» + private Map, «augmentField.type.name»«augmentField.type.generics.print»> «augmentField.name» = new HashMap<>(); + «ENDIF» + ''' + + def private print(List generics) '''«IF generics != null && !generics.empty»<«FOR generic : generics SEPARATOR ', '»«generic.name»«ENDFOR»>«ENDIF»''' + +} diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/Constants.java b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/Constants.java index bcc17906d6..8f5c456f87 100644 --- a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/Constants.java +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/Constants.java @@ -24,6 +24,8 @@ final class Constants { public static final String GAP = " "; public static final String COMMA = ","; + public static final String DOT = "."; + public static final String ASTERISK = "*"; public static final String NL = "\n"; public static final String SC = ";"; public static final String TAB = " "; diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/GeneratorJavaFile.java b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/GeneratorJavaFile.java index 55b8d777b7..1d2e60f511 100644 --- a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/GeneratorJavaFile.java +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/main/java/org/opendaylight/controller/sal/java/api/generator/GeneratorJavaFile.java @@ -23,6 +23,7 @@ public final class GeneratorJavaFile { private final CodeGenerator interfaceGenerator; private final ClassCodeGenerator classGenerator; private final EnumGenerator enumGenerator; + private final BuilderGenerator builderGenerator; private final Set genTypes; private final Set genTransferObjects; @@ -35,6 +36,7 @@ public final class GeneratorJavaFile { this.enumerations = new HashSet<>(); this.classGenerator = new ClassCodeGenerator(); this.enumGenerator = new EnumGenerator(); + this.builderGenerator = new BuilderGenerator(); } public GeneratorJavaFile(final Set types, final Set genTransferObjects, @@ -42,6 +44,7 @@ public final class GeneratorJavaFile { this.interfaceGenerator = new InterfaceGenerator(); this.classGenerator = new ClassCodeGenerator(); this.enumGenerator = new EnumGenerator(); + this.builderGenerator = new BuilderGenerator(); this.genTypes = types; this.genTransferObjects = genTransferObjects; @@ -51,14 +54,19 @@ public final class GeneratorJavaFile { public List generateToFile(final File parentDirectory) throws IOException { final List result = new ArrayList<>(); for (GeneratedType type : genTypes) { - final File genFile = generateTypeToJavaFile(parentDirectory, type, interfaceGenerator); + final File genFile = generateTypeToJavaFile(parentDirectory, type, interfaceGenerator, ""); + final File genBuilderFile = generateTypeToJavaFile(parentDirectory, type, builderGenerator, + BuilderGenerator.FILE_NAME_SUFFIX); if (genFile != null) { result.add(genFile); } + if (genBuilderFile != null) { + result.add(genBuilderFile); + } } for (GeneratedTransferObject transferObject : genTransferObjects) { - final File genFile = generateTypeToJavaFile(parentDirectory, transferObject, classGenerator); + final File genFile = generateTypeToJavaFile(parentDirectory, transferObject, classGenerator, ""); if (genFile != null) { result.add(genFile); @@ -66,7 +74,7 @@ public final class GeneratorJavaFile { } for (Enumeration enumeration : enumerations) { - final File genFile = generateTypeToJavaFile(parentDirectory, enumeration, enumGenerator); + final File genFile = generateTypeToJavaFile(parentDirectory, enumeration, enumGenerator, ""); if (genFile != null) { result.add(genFile); @@ -76,7 +84,7 @@ public final class GeneratorJavaFile { return result; } - private File generateTypeToJavaFile(final File parentDir, final Type type, final CodeGenerator generator) + private File generateTypeToJavaFile(final File parentDir, final Type type, final CodeGenerator generator, String fileNameSuffix) throws IOException { if (parentDir == null) { log.warn("Parent Directory not specified, files will be generated " @@ -95,7 +103,7 @@ public final class GeneratorJavaFile { if (!packageDir.exists()) { packageDir.mkdirs(); } - final File file = new File(packageDir, type.getName() + ".java"); + final File file = new File(packageDir, type.getName() + fileNameSuffix + ".java"); try (final FileWriter fw = new FileWriter(file)) { file.createNewFile(); diff --git a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/test/java/org/opendaylight/controller/sal/java/api/generator/test/GeneratorJavaFileTest.java b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/test/java/org/opendaylight/controller/sal/java/api/generator/test/GeneratorJavaFileTest.java index db42d32fe6..6a38050a4e 100644 --- a/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/test/java/org/opendaylight/controller/sal/java/api/generator/test/GeneratorJavaFileTest.java +++ b/opendaylight/sal/yang-prototype/code-generator/binding-java-api-generator/src/test/java/org/opendaylight/controller/sal/java/api/generator/test/GeneratorJavaFileTest.java @@ -90,10 +90,13 @@ public class GeneratorJavaFileTest { + "controller" + FS + "gen").list(); List filesList = Arrays.asList(files); - assertEquals(3, files.length); + assertEquals(6, files.length); assertTrue(filesList.contains("Type1.java")); assertTrue(filesList.contains("Type2.java")); assertTrue(filesList.contains("Type3.java")); + assertTrue(filesList.contains("Type1Builder.java")); + assertTrue(filesList.contains("Type2Builder.java")); + assertTrue(filesList.contains("Type3Builder.java")); } @Ignore -- 2.36.6