* @throws IllegalArgumentException
* <ul>
* <li>if <code>typeDefinition</code> equal null</li>
- * <li>if Qname of <code>typeDefinition</code> equal null</li>
+ * <li>if QName of <code>typeDefinition</code> equal null</li>
* <li>if name of <code>typeDefinition</code> equal null</li>
* </ul>
*/
* <ul>
* <li>if <code>basePackageName</code> is null</li>
* <li>if <code>typedef</code> is null</li>
- * <li>if Qname of <code>typedef</code> is null</li>
+ * <li>if QName of <code>typedef</code> is null</li>
* </ul>
*/
@SuppressWarnings({ "rawtypes", "unchecked" })
super(packageName, name);
}
+ /**
+ * Creates instance of this class with concrete package name and type name
+ * for already normalized identifier
+ *
+ * @param packageName
+ * string with the package name
+ * @param name
+ * string with the name for referenced type
+ * @param isNormalized
+ * indicates if identifier name is normalized
+ */
+ public ReferencedTypeImpl(String packageName, String name, boolean isNormalized) {
+ super(packageName, name, isNormalized);
+ }
+
@Override
public String toString() {
StringBuilder builder = new StringBuilder();
@Beta
public final class BuilderGenerator implements CodeGenerator {
+ /**
+ * Constant used as suffix for builder name.
+ */
+ public static final String BUILDER = "Builder";
+
@Override
public String generate(Type type) {
if ((type instanceof GeneratedType) && !(type instanceof GeneratedTransferObject)) {
@Override
public Identifier getUnitName(Type type) {
- return new UnitName(type.getName());
+ return new UnitName(type.getName() + BUILDER);
}
}
import com.google.common.annotations.Beta;
import com.google.common.base.Preconditions;
+import com.google.common.base.Splitter;
+import java.io.BufferedWriter;
+import java.io.File;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Collection;
+import java.util.Iterator;
import java.util.List;
import org.opendaylight.mdsal.binding.javav2.model.api.CodeGenerator;
+import org.opendaylight.mdsal.binding.javav2.model.api.GeneratedTransferObject;
import org.opendaylight.mdsal.binding.javav2.model.api.Type;
+import org.opendaylight.mdsal.binding.javav2.model.api.UnitName;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.sonatype.plexus.build.incremental.BuildContext;
public final class GeneratorJavaFile {
private static final Logger LOG = LoggerFactory.getLogger(GeneratorJavaFile.class);
+ private static final Splitter BSDOT_SPLITTER = Splitter.on(".");
/**
* List of <code>CodeGenerator</code> instances.
this.buildContext = Preconditions.checkNotNull(buildContext);
this.types = Preconditions.checkNotNull(types);
generators.add(new EnumGenerator());
- //TODO: finish generators
-// generators.add(new InterfaceGenerator());
+ generators.add(new InterfaceGenerator());
+ generators.add(new BuilderGenerator());
+ //TODO: finish generators below
// generators.add(new TOGenerator());
-// generators.add(new BuilderGenerator());
+ }
+
+ /**
+ * Generates <code>List</code> of files for collection of types. All files are stored
+ * to sub-folders of base directory <code>persistentSourcesDirectory</code>. Subdirectories
+ * are generated according to packages to which the type belongs (e. g. if
+ * type belongs to the package <i>org.pcg</i> then in <code>persistentSourcesDirectory</code>
+ * is created directory <i>org</i> which contains <i>pcg</i>).
+ *
+ * @param generatedSourcesDirectory expected output directory for generated sources configured by
+ * user
+ * @param persistentSourcesDirectory base directory
+ * @return list of generated files
+ * @throws IOException thrown in case of I/O error
+ */
+ public List<File> generateToFile(final File generatedSourcesDirectory, final File persistentSourcesDirectory)
+ throws IOException {
+ final List<File> result = new ArrayList<>();
+ for (Type type : types) {
+ if (type != null) {
+ for (CodeGenerator generator : generators) {
+ File generatedJavaFile = null;
+ if (type instanceof GeneratedTransferObject
+ && ((GeneratedTransferObject) type).isUnionTypeBuilder()) {
+ File packageDir = packageToDirectory(persistentSourcesDirectory, type.getPackageName());
+ File file = new File(packageDir, generator.getUnitName(type) + ".java");
+ if (!file.exists()) {
+ generatedJavaFile = generateTypeToJavaFile(persistentSourcesDirectory, type, generator);
+ }
+ } else {
+ generatedJavaFile = generateTypeToJavaFile(generatedSourcesDirectory, type, generator);
+ }
+ if (generatedJavaFile != null) {
+ result.add(generatedJavaFile);
+ }
+ }
+ }
+ }
+ return result;
+ }
+
+ /**
+ * Creates the package directory path as concatenation of
+ * <code>parentDirectory</code> and parsed <code>packageName</code>. The
+ * parsing of <code>packageName</code> is realized as replacement of the
+ * package name dots with the file system separator.
+ *
+ * @param parentDirectory
+ * <code>File</code> object with reference to parent directory
+ * @param packageName
+ * string with the name of the package
+ * @return <code>File</code> object which refers to the new directory for
+ * package <code>packageName</code>
+ */
+ public static File packageToDirectory(final File parentDirectory, final String packageName) {
+ if (packageName == null) {
+ throw new IllegalArgumentException("Package Name cannot be NULL!");
+ }
+
+ final StringBuilder dirPathBuilder = new StringBuilder();
+ final Iterator<String> packageElementsItr = BSDOT_SPLITTER.split(packageName).iterator();
+ if (packageElementsItr.hasNext()) {
+ dirPathBuilder.append(packageElementsItr.next());
+ }
+
+ while (packageElementsItr.hasNext()) {
+ dirPathBuilder.append(File.separator);
+ dirPathBuilder.append(packageElementsItr.next());
+ }
+
+ return new File(parentDirectory, dirPathBuilder.toString());
+ }
+
+ /**
+ * Generates <code>File</code> for <code>type</code>. All files are stored
+ * to sub-folders of base directory <code>parentDir</code>. Subdirectories
+ * are generated according to packages to which the type belongs (e. g. if
+ * type belongs to the package <i>org.pcg</i> then in <code>parentDir</code>
+ * is created directory <i>org</i> which contains <i>pcg</i>).
+ *
+ * @param parentDir
+ * directory where should be the new file generated
+ * @param type
+ * JAVA <code>Type</code> for which should be JAVA source code
+ * generated
+ * @param generator
+ * code generator which is used for generating of the source code
+ * @return file which contains JAVA source code
+ * @throws IOException
+ * if the error during writing to the file occurs
+ * @throws IllegalArgumentException
+ * if <code>type</code> equals <code>null</code>
+ * @throws IllegalStateException
+ * if string with generated code is empty
+ */
+ private File generateTypeToJavaFile(final File parentDir, final Type type, final CodeGenerator generator)
+ throws IOException {
+ if (parentDir == null) {
+ LOG.warn("Parent Directory not specified, files will be generated "
+ + "accordingly to generated Type package path.");
+ }
+ if (type == null) {
+ LOG.error("Cannot generate Type into Java File because " + "Generated Type is NULL!");
+ throw new IllegalArgumentException("Generated Type Cannot be NULL!");
+ }
+ if (generator == null) {
+ LOG.error("Cannot generate Type into Java File because " + "Code Generator instance is NULL!");
+ throw new IllegalArgumentException("Code Generator Cannot be NULL!");
+ }
+ final File packageDir = packageToDirectory(parentDir, type.getPackageName());
+
+ if (!packageDir.exists()) {
+ packageDir.mkdirs();
+ }
+
+ if (generator.isAcceptable(type)) {
+ final String generatedCode = generator.generate(type);
+ Preconditions.checkState(!generatedCode.isEmpty(), "Generated code should not be empty!");
+ final File file = new File(packageDir, ((UnitName) generator.getUnitName(type)).getValue() + ".java");
+
+ if (file.exists()) {
+ LOG.warn("Naming conflict for type '{}': file with same name already exists and will not be generated.",
+ type.getFullyQualifiedName());
+ return null;
+ }
+
+ try (final OutputStream stream = buildContext.newFileOutputStream(file)) {
+ try (final Writer fw = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
+ try (final BufferedWriter bw = new BufferedWriter(fw)) {
+ bw.write(generatedCode);
+ }
+ } catch (IOException e) {
+ LOG.error("Failed to write generate output into {}", file.getPath(), e);
+ throw e;
+ }
+ }
+ return file;
+
+ }
+ return null;
}
}
final String aPackage = getPackage(fullyQualifiedName);
final String name = getName(fullyQualifiedName);
final GeneratedTOBuilderImpl generatedTOBuilder = new GeneratedTOBuilderImpl(aPackage, name);
- final ReferencedTypeImpl referencedType = new ReferencedTypeImpl(aPackage, name);
+ final ReferencedTypeImpl referencedType = new ReferencedTypeImpl(aPackage, name, true);
final ReferencedTypeImpl generic = new ReferencedTypeImpl(getType().getPackageName(),
- getType().getName());
+ getType().getName(), true);
final ParameterizedType parametrizedReturnType = Types.parameterizedTypeFor(referencedType, generic);
generatedTOBuilder.addMethod(method.getName()).setReturnType(parametrizedReturnType);
augmentField = propertyFromGetter(generatedTOBuilder.toInstance().getMethodDefinitions().get(0));
final String generatedConstants = String.join("\n", strings);
- final List<String> innerClassesBuilder = new ArrayList<>(getType().getEnclosedTypes().size());
+ final List<String> innerClasses = new ArrayList<>(getType().getEnclosedTypes().size());
for (GeneratedType innerClass : getType().getEnclosedTypes()) {
if (innerClass instanceof GeneratedTransferObject) {
if (((GeneratedTransferObject) innerClass).isUnionType()) {
final UnionRenderer unionRenderer = new UnionRenderer((GeneratedTransferObject) innerClass);
- innerClassesBuilder.add(unionRenderer.body());
+ innerClasses.add(unionRenderer.body());
this.getImportMap().putAll(unionRenderer.getImportMap());
} else {
final ClassRenderer classRenderer = new ClassRenderer((GeneratedTransferObject) innerClass);
- innerClassesBuilder.add(classRenderer.generateAsInnerClass());
+ innerClasses.add(classRenderer.generateAsInnerClass());
this.getImportMap().putAll(classRenderer.getImportMap());
}
}
}
- final String innerClasses = String.join("\n", strings);
+ final String generatedInnerClasses = String.join("\n", innerClasses);
return interfaceTemplate.render(getType(), enums, mainAnnotations, methodList, generatedImports,
- generatedConstants, innerClasses).body();
+ generatedConstants, generatedInnerClasses).body();
}
private boolean isAccessor (final MethodSignature maybeGetter) {
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
import java.util.TreeMap;
+import java.util.function.Function;
import org.opendaylight.mdsal.binding.javav2.generator.util.Types;
import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.modelProviderTemplate;
import org.opendaylight.mdsal.binding.javav2.java.api.generator.txt.yangModuleInfoTemplate;
private final Map<String, String> importMap = new HashMap<>();
private final String packageName;
private final String modelBindingProviderName;
+ private final Function<Module, Optional<String>> moduleFilePathResolver;
- public YangModuleInfoTemplateRenderer(final Module module, final SchemaContext ctx) {
+
+ public YangModuleInfoTemplateRenderer(final Module module, final SchemaContext ctx, final Function<Module, Optional<String>> moduleFilePathResolver) {
Preconditions.checkArgument(module != null, "Module must not be null.");
this.module = module;
this.ctx = ctx;
this.packageName = getRootPackageName(module);
+ this.moduleFilePathResolver = moduleFilePathResolver;
final StringBuilder sb = new StringBuilder();
sb.append(packageName)
}
return sorted.lastEntry().getValue();
}
+
+ public String getModelBindingProviderName() {
+ return modelBindingProviderName;
+ }
}
\ No newline at end of file
@generateAugmentField(isPrivate: Boolean) = {
@if(augmentField != null) {
@if(isPrivate) {private }
- @{importedNames.get("map")}<@{importedNames.get("class")}<? extends@{importedNames.get("augmentFieldReturnType")}>,
- @{importedNames.get("augmentFieldReturnType")}>@{augmentField.getName} = @{importedNames.get("collections")}.emptyMap();
+ @{importedNames.get("map")}<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>,
+ @{importedNames.get("augmentFieldReturnType")}> @{augmentField.getName} = @{importedNames.get("collections")}.emptyMap();
}
}
break;
case 1:
final @{importedNames.get("map")}.Entry<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}> e = base.@{augmentField.getName}.entrySet().iterator().next();
- this.@{augmentField.getName} = @{importedNames.get("collections")}.<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}>singletonMap(e.getKey(), e.getValue());
+ this.@{augmentField.getName} = @{importedNames.get("collections")}.<@{importedNames.get("class")}<? extends @{importedNames.get("augmentFieldReturnType")}>, @{importedNames.get("augmentFieldReturnType")}> singletonMap(e.getKey(), e.getValue());
break;
default :
this.@{augmentField.getName} = new @{importedNames.get("hashMap")}<>(base.@{augmentField.getName});
package org.opendaylight.mdsal.binding.javav2.maven.api.gen.plugin;
import com.google.common.annotations.Beta;
+import com.google.common.base.Joiner;
import com.google.common.base.Preconditions;
+import com.google.common.collect.ImmutableSet;
+import com.google.common.collect.ImmutableSet.Builder;
+import java.io.BufferedWriter;
import java.io.File;
import java.io.IOException;
-import java.util.ArrayList;
+import java.io.OutputStream;
+import java.io.OutputStreamWriter;
+import java.io.Writer;
+import java.nio.charset.StandardCharsets;
import java.util.Collection;
import java.util.List;
import java.util.Map;
+import java.util.Optional;
import java.util.Set;
+import java.util.function.Function;
import org.apache.maven.project.MavenProject;
import org.opendaylight.mdsal.binding.javav2.generator.api.BindingGenerator;
import org.opendaylight.mdsal.binding.javav2.generator.impl.BindingGeneratorImpl;
+import org.opendaylight.mdsal.binding.javav2.java.api.generator.GeneratorJavaFile;
+import org.opendaylight.mdsal.binding.javav2.java.api.generator.renderers.YangModuleInfoTemplateRenderer;
import org.opendaylight.mdsal.binding.javav2.model.api.Type;
+import org.opendaylight.mdsal.binding.javav2.spec.runtime.YangModelBindingProvider;
+import org.opendaylight.mdsal.binding.javav2.util.BindingMapping;
import org.opendaylight.yangtools.yang.model.api.Module;
import org.opendaylight.yangtools.yang.model.api.SchemaContext;
import org.opendaylight.yangtools.yang2sources.spi.BasicCodeGenerator;
public final class CodeGeneratorImpl implements BasicCodeGenerator, BuildContextAware, MavenProjectAware {
private static final Logger LOG = LoggerFactory.getLogger(CodeGeneratorImpl.class);
+ private static final String FS = File.separator;
private BuildContext buildContext;
private File projectBaseDir;
private Map<String, String> additionalConfig;
private MavenProject mavenProject;
private File resourceBaseDir;
- //TODO: bind this all together with BindingGeneratorImpl and GeneratorJavaFile
-
/**
* This method is called from mojo (maven goal)
- *
* @param context
* parsed from YANG files
* @param outputBaseDir
* user
* @param currentModules
* YANG modules parsed from yangFilesRootDir
+ * @param moduleResourcePathResolver
+ * Function converting a local module to the packaged resource path
* @return
* @throws IOException
*/
@Override
- public Collection<File> generateSources(SchemaContext context, File outputBaseDir, Set<Module> currentModules) throws IOException {
-
+ public Collection<File> generateSources(SchemaContext context, File outputBaseDir, Set<Module> currentModules, Function<Module, Optional<String>> moduleResourcePathResolver) throws IOException {
final BindingGenerator bindingGenerator = new BindingGeneratorImpl(true);
final List<Type> types = bindingGenerator.generateTypes(context, currentModules);
+ final GeneratorJavaFile generator = new GeneratorJavaFile(buildContext, types);
+
+ File persistentSourcesDir = null;
+ if (additionalConfig != null) {
+ String persistentSourcesPath = additionalConfig.get("persistentSourcesDir");
+ if (persistentSourcesPath != null) {
+ persistentSourcesDir = new File(persistentSourcesPath);
+ }
+ }
+ if (persistentSourcesDir == null) {
+ persistentSourcesDir = new File(projectBaseDir, "src" + FS + "main" + FS + "java");
+ }
- List<File> result = new ArrayList<>();
+ List<File> result = generator.generateToFile(outputBaseDir, persistentSourcesDir);
+
+ result.addAll(generateModuleInfos(outputBaseDir, currentModules, context, moduleResourcePathResolver));
return result;
}
this.mavenProject = project;
this.projectBaseDir = project.getBasedir();
}
+
+ @Override
+ public Collection<File> generateSources(SchemaContext context, File outputBaseDir, Set<Module> currentModules) throws IOException {
+ return generateSources(context, outputBaseDir, currentModules,
+ m -> Optional.of("/" + m.getModuleSourcePath().replace(File.separator, "/")));
+ }
+
+ private Collection<? extends File> generateModuleInfos(final File outputBaseDir, final Set<Module> yangModules,
+ final SchemaContext context, final Function<Module, Optional<String>> moduleResourcePathResolver) {
+ Builder<File> result = ImmutableSet.builder();
+ Builder<String> bindingProviders = ImmutableSet.builder();
+ for (Module module : yangModules) {
+ Builder<String> currentProvidersBuilder = ImmutableSet.builder();
+ Set<File> moduleInfoProviders = generateYangModuleInfo(outputBaseDir, module, context,
+ moduleResourcePathResolver, currentProvidersBuilder);
+ ImmutableSet<String> currentProviders = currentProvidersBuilder.build();
+ LOG.info("Adding ModuleInfo providers {}", currentProviders);
+ bindingProviders.addAll(currentProviders);
+ result.addAll(moduleInfoProviders);
+ }
+
+ result.add(writeMetaInfServices(resourceBaseDir, YangModelBindingProvider.class, bindingProviders.build()));
+ return result.build();
+ }
+
+ private File writeMetaInfServices(final File outputBaseDir, final Class<YangModelBindingProvider> serviceClass,
+ final ImmutableSet<String> services) {
+ File metainfServicesFolder = new File(outputBaseDir, "META-INF" + File.separator + "services");
+ metainfServicesFolder.mkdirs();
+ File serviceFile = new File(metainfServicesFolder, serviceClass.getName());
+
+ String src = Joiner.on('\n').join(services);
+
+ return writeFile(serviceFile, src);
+ }
+
+ private Set<File> generateYangModuleInfo(final File outputBaseDir, final Module module, final SchemaContext ctx,
+ final Function<Module, Optional<String>> moduleResourcePathResolver, final Builder<String> providerSourceSet) {
+
+ Builder<File> generatedFiles = ImmutableSet.builder();
+ final YangModuleInfoTemplateRenderer template = new YangModuleInfoTemplateRenderer(module, ctx,
+ moduleResourcePathResolver);
+ String moduleInfoSource = template.generateTemplate();
+ if (moduleInfoSource.isEmpty()) {
+ throw new IllegalStateException("Generated code should not be empty!");
+ }
+ String providerSource = template.generateModelProvider();
+
+ final File packageDir = GeneratorJavaFile.packageToDirectory(outputBaseDir,
+ BindingMapping.getRootPackageName(module));
+
+ generatedFiles.add(writeJavaSource(packageDir, BindingMapping.MODULE_INFO_CLASS_NAME, moduleInfoSource));
+ generatedFiles
+ .add(writeJavaSource(packageDir, BindingMapping.MODEL_BINDING_PROVIDER_CLASS_NAME, providerSource));
+ providerSourceSet.add(template.getModelBindingProviderName());
+
+ return generatedFiles.build();
+
+ }
+
+ private File writeFile(final File file, final String source) {
+ try (final OutputStream stream = buildContext.newFileOutputStream(file)) {
+ try (final Writer fw = new OutputStreamWriter(stream, StandardCharsets.UTF_8)) {
+ try (final BufferedWriter bw = new BufferedWriter(fw)) {
+ bw.write(source);
+ }
+ } catch (Exception e) {
+ LOG.error("Could not write file: {}",file,e);
+ }
+ } catch (Exception e) {
+ LOG.error("Could not create file: {}",file,e);
+ }
+ return file;
+ }
+
+ private File writeJavaSource(final File packageDir, final String className, final String source) {
+ if (!packageDir.exists()) {
+ packageDir.mkdirs();
+ }
+ final File file = new File(packageDir, className + ".java");
+ writeFile(file, source);
+ return file;
+ }
}