blob: 6fd733169c2404a2e6e294a0e7ad29cf40e42864 [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) {
Ray Milkey5504bd22019-03-22 16:24:38 -0700145 if (name.startsWith("_")) {
146 // Static property - just leave it as is, not for inclusion in the cfg defs
147 return null;
148 }
Thomas Vachuska4a347632018-09-11 11:53:08 -0700149 JavaField field = javaClass.getFieldByName(name);
150 if (field != null) {
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700151 String comment = field.getComment();
152 return comment != null ? comment : NO_DESCRIPTION;
Thomas Vachuska4a347632018-09-11 11:53:08 -0700153 }
Ray Milkeybd508ed2019-03-19 14:22:02 -0700154 throw new IllegalStateException("cfgdef could not find a variable named " + name + " in " + javaClass.getName());
Thomas Vachuska4a347632018-09-11 11:53:08 -0700155 }
156
157 private String elaborate(AnnotationValue value) {
158 if (value instanceof Add) {
159 return elaborate(((Add) value).getLeft()) + elaborate(((Add) value).getRight());
160 } else if (value instanceof FieldRef) {
Thomas Vachuska0054d342018-10-29 10:19:47 -0700161 return elaborate((FieldRef) value);
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700162 } else if (value != null) {
Thomas Vachuska4a347632018-09-11 11:53:08 -0700163 return stripped(value.toString());
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700164 } else {
165 return "";
Thomas Vachuska4a347632018-09-11 11:53:08 -0700166 }
167 }
168
Thomas Vachuska0054d342018-10-29 10:19:47 -0700169 private String elaborate(FieldRef field) {
170 String name = field.getName();
171 String value = constants.get(name);
172 if (value != null) {
173 return stripped(value);
174 }
175 throw new IllegalStateException("Constant " + name + " cannot be elaborated;" +
176 " value not in the same compilation context");
177 }
178
Thomas Vachuska4a347632018-09-11 11:53:08 -0700179 private String stripped(String s) {
180 return s.trim().replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
181 }
182
183 private void writeCatalog(JarOutputStream jar, JavaClass javaClass, List<String> lines)
184 throws IOException {
185 String name = javaClass.getPackageName().replace('.', '/') + "/" + javaClass.getName() + EXT;
186 jar.putNextEntry(new JarEntry(name));
187 jar.write("# This file is auto-generated\n".getBytes(UTF_8));
188 for (String line : lines) {
189 jar.write(line.getBytes(UTF_8));
190 }
191 jar.closeEntry();
192 }
193
194}