Porting cfgdef generation into a simple and stand-alone command-line utility.
Change-Id: I1c5731cb15635232e4c7a55a8373b9ee6b282c06
diff --git a/tools/build/cfgdef/BUILD b/tools/build/cfgdef/BUILD
new file mode 100644
index 0000000..8098840
--- /dev/null
+++ b/tools/build/cfgdef/BUILD
@@ -0,0 +1,14 @@
+CFGDEF_EXECUTABLE = "cfgdef_generator"
+
+COMPILE_DEPS = JACKSON + [
+ "@com_google_guava_guava//jar",
+ "@qdox//jar",
+]
+
+java_binary(
+ name = CFGDEF_EXECUTABLE,
+ srcs = glob(["src/main/java/org/onosproject/cfgdef/CfgDefGenerator.java"]),
+ main_class = "org.onosproject.cfgdef.CfgDefGenerator",
+ visibility = ["//visibility:public"],
+ deps = COMPILE_DEPS,
+)
diff --git a/tools/build/cfgdef/src/main/java/org/onosproject/cfgdef/CfgDefGenerator.java b/tools/build/cfgdef/src/main/java/org/onosproject/cfgdef/CfgDefGenerator.java
new file mode 100644
index 0000000..696918d
--- /dev/null
+++ b/tools/build/cfgdef/src/main/java/org/onosproject/cfgdef/CfgDefGenerator.java
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2018-present Open Networking Foundation
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package org.onosproject.cfgdef;
+
+import com.google.common.collect.Maps;
+import com.thoughtworks.qdox.JavaProjectBuilder;
+import com.thoughtworks.qdox.model.JavaAnnotation;
+import com.thoughtworks.qdox.model.JavaClass;
+import com.thoughtworks.qdox.model.JavaField;
+import com.thoughtworks.qdox.model.expression.Add;
+import com.thoughtworks.qdox.model.expression.AnnotationValue;
+import com.thoughtworks.qdox.model.expression.AnnotationValueList;
+import com.thoughtworks.qdox.model.expression.FieldRef;
+
+import java.io.File;
+import java.io.FileOutputStream;
+import java.io.IOException;
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.jar.JarEntry;
+import java.util.jar.JarOutputStream;
+
+/**
+ * Produces ONOS component configuration catalogue resources.
+ */
+public class CfgDefGenerator {
+
+ private static final String COMPONENT = "org.osgi.service.component.annotations.Component";
+ private static final String PROPERTY = "property";
+ private static final String SEP = "|";
+ private static final String UTF_8 = "UTF-8";
+ private static final String EXT = ".cfgdef";
+ private static final String STRING = "STRING";
+
+ private final File resourceJar;
+ private final JavaProjectBuilder builder;
+
+ private final Map<String, String> constants = Maps.newHashMap();
+
+ public static void main(String[] args) throws IOException {
+ if (args.length < 2) {
+ System.err.println("usage: cfgdef outputJar javaSource javaSource ...");
+ System.exit(1);
+ }
+ CfgDefGenerator gen = new CfgDefGenerator(args[0], Arrays.copyOfRange(args, 1, args.length));
+ gen.analyze();
+ gen.generate();
+ }
+
+ private CfgDefGenerator(String resourceJarPath, String[] sourceFilePaths) {
+ this.resourceJar = new File(resourceJarPath);
+ this.builder = new JavaProjectBuilder();
+ Arrays.stream(sourceFilePaths).forEach(filename -> {
+ try {
+ builder.addSource(new File(filename));
+ } catch (IOException e) {
+ throw new IllegalArgumentException("Unable to open file", e);
+ }
+ });
+ }
+
+ public void analyze() {
+ builder.getClasses().forEach(this::collectConstants);
+ }
+
+ private void collectConstants(JavaClass javaClass) {
+ javaClass.getFields().stream()
+ .filter(f -> f.isStatic() && f.isFinal() && !f.isPrivate())
+ .forEach(f -> constants.put(f.getName(), f.getInitializationExpression()));
+ }
+
+ public void generate() throws IOException {
+ JarOutputStream jar = new JarOutputStream(new FileOutputStream(resourceJar));
+ for (JavaClass javaClass : builder.getClasses()) {
+ processClass(jar, javaClass);
+ }
+ jar.close();
+ }
+
+ private void processClass(JarOutputStream jar, JavaClass javaClass) throws IOException {
+ Optional<JavaAnnotation> annotation = javaClass.getAnnotations().stream()
+ .filter(ja -> ja.getType().getName().equals(COMPONENT))
+ .findFirst();
+ if (annotation.isPresent()) {
+ AnnotationValue property = annotation.get().getProperty(PROPERTY);
+ List<String> lines = new ArrayList<>();
+ if (property instanceof AnnotationValueList) {
+ AnnotationValueList list = (AnnotationValueList) property;
+ list.getValueList().forEach(v -> processProperty(lines, javaClass, v));
+ } else {
+ processProperty(lines, javaClass, property);
+ }
+
+ if (!lines.isEmpty()) {
+ writeCatalog(jar, javaClass, lines);
+ }
+ }
+ }
+
+ private void processProperty(List<String> lines, JavaClass javaClass,
+ AnnotationValue value) {
+ String s = elaborate(value);
+ String pex[] = s.split("=", 2);
+
+ if (pex.length == 2) {
+ String rex[] = pex[0].split(":", 2);
+ String name = rex[0];
+ String type = rex.length == 2 ? rex[1].toUpperCase() : STRING;
+ String def = pex[1];
+ String desc = description(javaClass, name);
+
+ String line = name + SEP + type + SEP + def + SEP + desc + "\n";
+ lines.add(line);
+ System.err.print(line);
+ }
+ }
+
+ // Retrieve description from a comment preceeding the field named the same
+ // as the property or
+ // TODO: from an annotated comment.
+ private String description(JavaClass javaClass, String name) {
+ JavaField field = javaClass.getFieldByName(name);
+ if (field != null) {
+ return field.getComment();
+ }
+ return "no description provided";
+ }
+
+ private String elaborate(AnnotationValue value) {
+ if (value instanceof Add) {
+ return elaborate(((Add) value).getLeft()) + elaborate(((Add) value).getRight());
+ } else if (value instanceof FieldRef) {
+ return stripped(constants.get(((FieldRef) value).getName()));
+ } else {
+ return stripped(value.toString());
+ }
+ }
+
+ private String stripped(String s) {
+ return s.trim().replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
+ }
+
+ private void writeCatalog(JarOutputStream jar, JavaClass javaClass, List<String> lines)
+ throws IOException {
+ String name = javaClass.getPackageName().replace('.', '/') + "/" + javaClass.getName() + EXT;
+ jar.putNextEntry(new JarEntry(name));
+ jar.write("# This file is auto-generated\n".getBytes(UTF_8));
+ for (String line : lines) {
+ jar.write(line.getBytes(UTF_8));
+ }
+ jar.closeEntry();
+ }
+
+}