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();
+    }
+
+}