blob: c46b583c00fe6262c09a3178e9e3593136cf8ac9 [file] [log] [blame]
Thomas Vachuska4a347632018-09-11 11:53:08 -07001/*
2 * Copyright 2018-present Open Networking Foundation
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16package org.onosproject.cfgdef;
17
18import com.google.common.collect.Maps;
19import com.thoughtworks.qdox.JavaProjectBuilder;
20import com.thoughtworks.qdox.model.JavaAnnotation;
21import com.thoughtworks.qdox.model.JavaClass;
22import com.thoughtworks.qdox.model.JavaField;
23import com.thoughtworks.qdox.model.expression.Add;
24import com.thoughtworks.qdox.model.expression.AnnotationValue;
25import com.thoughtworks.qdox.model.expression.AnnotationValueList;
26import com.thoughtworks.qdox.model.expression.FieldRef;
Thomas Vachuskabef430b2018-10-22 10:55:47 -070027import com.thoughtworks.qdox.parser.ParseException;
Thomas Vachuska4a347632018-09-11 11:53:08 -070028
29import java.io.File;
30import java.io.FileOutputStream;
31import java.io.IOException;
32import java.util.ArrayList;
33import java.util.Arrays;
34import java.util.List;
35import java.util.Map;
36import java.util.Optional;
37import java.util.jar.JarEntry;
38import java.util.jar.JarOutputStream;
39
40/**
41 * Produces ONOS component configuration catalogue resources.
42 */
43public class CfgDefGenerator {
44
45 private static final String COMPONENT = "org.osgi.service.component.annotations.Component";
46 private static final String PROPERTY = "property";
47 private static final String SEP = "|";
48 private static final String UTF_8 = "UTF-8";
Thomas Vachuskabef430b2018-10-22 10:55:47 -070049 private static final String JAVA = ".java";
Thomas Vachuska4a347632018-09-11 11:53:08 -070050 private static final String EXT = ".cfgdef";
51 private static final String STRING = "STRING";
Thomas Vachuskabef430b2018-10-22 10:55:47 -070052 private static final String NO_DESCRIPTION = "no description provided";
Thomas Vachuska4a347632018-09-11 11:53:08 -070053
54 private final File resourceJar;
55 private final JavaProjectBuilder builder;
56
57 private final Map<String, String> constants = Maps.newHashMap();
58
59 public static void main(String[] args) throws IOException {
60 if (args.length < 2) {
61 System.err.println("usage: cfgdef outputJar javaSource javaSource ...");
62 System.exit(1);
63 }
64 CfgDefGenerator gen = new CfgDefGenerator(args[0], Arrays.copyOfRange(args, 1, args.length));
65 gen.analyze();
66 gen.generate();
67 }
68
69 private CfgDefGenerator(String resourceJarPath, String[] sourceFilePaths) {
70 this.resourceJar = new File(resourceJarPath);
71 this.builder = new JavaProjectBuilder();
72 Arrays.stream(sourceFilePaths).forEach(filename -> {
73 try {
Thomas Vachuskabef430b2018-10-22 10:55:47 -070074 if (filename.endsWith(JAVA))
Thomas Vachuska4a347632018-09-11 11:53:08 -070075 builder.addSource(new File(filename));
Thomas Vachuskabef430b2018-10-22 10:55:47 -070076 } catch (ParseException e) {
77 // When unable to parse, skip the source; leave it to javac to fail.
Thomas Vachuska4a347632018-09-11 11:53:08 -070078 } catch (IOException e) {
79 throw new IllegalArgumentException("Unable to open file", e);
80 }
81 });
82 }
83
84 public void analyze() {
85 builder.getClasses().forEach(this::collectConstants);
86 }
87
88 private void collectConstants(JavaClass javaClass) {
89 javaClass.getFields().stream()
90 .filter(f -> f.isStatic() && f.isFinal() && !f.isPrivate())
91 .forEach(f -> constants.put(f.getName(), f.getInitializationExpression()));
92 }
93
94 public void generate() throws IOException {
95 JarOutputStream jar = new JarOutputStream(new FileOutputStream(resourceJar));
96 for (JavaClass javaClass : builder.getClasses()) {
97 processClass(jar, javaClass);
98 }
99 jar.close();
100 }
101
102 private void processClass(JarOutputStream jar, JavaClass javaClass) throws IOException {
103 Optional<JavaAnnotation> annotation = javaClass.getAnnotations().stream()
104 .filter(ja -> ja.getType().getName().equals(COMPONENT))
105 .findFirst();
106 if (annotation.isPresent()) {
107 AnnotationValue property = annotation.get().getProperty(PROPERTY);
108 List<String> lines = new ArrayList<>();
109 if (property instanceof AnnotationValueList) {
110 AnnotationValueList list = (AnnotationValueList) property;
111 list.getValueList().forEach(v -> processProperty(lines, javaClass, v));
112 } else {
113 processProperty(lines, javaClass, property);
114 }
115
116 if (!lines.isEmpty()) {
117 writeCatalog(jar, javaClass, lines);
118 }
119 }
120 }
121
122 private void processProperty(List<String> lines, JavaClass javaClass,
123 AnnotationValue value) {
124 String s = elaborate(value);
125 String pex[] = s.split("=", 2);
126
127 if (pex.length == 2) {
128 String rex[] = pex[0].split(":", 2);
129 String name = rex[0];
130 String type = rex.length == 2 ? rex[1].toUpperCase() : STRING;
131 String def = pex[1];
132 String desc = description(javaClass, name);
133
Thomas Vachuska9b6e45c2018-10-31 15:22:32 -0700134 if (desc != null) {
135 String line = name + SEP + type + SEP + def + SEP + desc + "\n";
136 lines.add(line);
137 }
Thomas Vachuska4a347632018-09-11 11:53:08 -0700138 }
139 }
140
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700141 // Retrieve description from a comment preceding the field named the same
Thomas Vachuska4a347632018-09-11 11:53:08 -0700142 // as the property or
143 // TODO: from an annotated comment.
144 private String description(JavaClass javaClass, String name) {
145 JavaField field = javaClass.getFieldByName(name);
146 if (field != null) {
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700147 String comment = field.getComment();
148 return comment != null ? comment : NO_DESCRIPTION;
Thomas Vachuska4a347632018-09-11 11:53:08 -0700149 }
Thomas Vachuska9b6e45c2018-10-31 15:22:32 -0700150 return null;
Thomas Vachuska4a347632018-09-11 11:53:08 -0700151 }
152
153 private String elaborate(AnnotationValue value) {
154 if (value instanceof Add) {
155 return elaborate(((Add) value).getLeft()) + elaborate(((Add) value).getRight());
156 } else if (value instanceof FieldRef) {
Thomas Vachuska0054d342018-10-29 10:19:47 -0700157 return elaborate((FieldRef) value);
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700158 } else if (value != null) {
Thomas Vachuska4a347632018-09-11 11:53:08 -0700159 return stripped(value.toString());
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700160 } else {
161 return "";
Thomas Vachuska4a347632018-09-11 11:53:08 -0700162 }
163 }
164
Thomas Vachuska0054d342018-10-29 10:19:47 -0700165 private String elaborate(FieldRef field) {
166 String name = field.getName();
167 String value = constants.get(name);
168 if (value != null) {
169 return stripped(value);
170 }
171 throw new IllegalStateException("Constant " + name + " cannot be elaborated;" +
172 " value not in the same compilation context");
173 }
174
Thomas Vachuska4a347632018-09-11 11:53:08 -0700175 private String stripped(String s) {
176 return s.trim().replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
177 }
178
179 private void writeCatalog(JarOutputStream jar, JavaClass javaClass, List<String> lines)
180 throws IOException {
181 String name = javaClass.getPackageName().replace('.', '/') + "/" + javaClass.getName() + EXT;
182 jar.putNextEntry(new JarEntry(name));
183 jar.write("# This file is auto-generated\n".getBytes(UTF_8));
184 for (String line : lines) {
185 jar.write(line.getBytes(UTF_8));
186 }
187 jar.closeEntry();
188 }
189
190}