blob: 3e3d109611997d1fae4211d28fa89d3650dbbd12 [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
pierventre13a72352020-10-27 16:08:48 +010018import com.google.common.collect.Lists;
Thomas Vachuska4a347632018-09-11 11:53:08 -070019import com.google.common.collect.Maps;
20import com.thoughtworks.qdox.JavaProjectBuilder;
21import com.thoughtworks.qdox.model.JavaAnnotation;
22import com.thoughtworks.qdox.model.JavaClass;
23import com.thoughtworks.qdox.model.JavaField;
24import com.thoughtworks.qdox.model.expression.Add;
25import com.thoughtworks.qdox.model.expression.AnnotationValue;
26import com.thoughtworks.qdox.model.expression.AnnotationValueList;
27import com.thoughtworks.qdox.model.expression.FieldRef;
Thomas Vachuskabef430b2018-10-22 10:55:47 -070028import com.thoughtworks.qdox.parser.ParseException;
Thomas Vachuska4a347632018-09-11 11:53:08 -070029
30import java.io.File;
31import java.io.FileOutputStream;
32import java.io.IOException;
33import java.util.ArrayList;
34import java.util.Arrays;
35import java.util.List;
36import java.util.Map;
37import java.util.Optional;
38import java.util.jar.JarEntry;
39import java.util.jar.JarOutputStream;
40
41/**
42 * Produces ONOS component configuration catalogue resources.
43 */
44public class CfgDefGenerator {
45
46 private static final String COMPONENT = "org.osgi.service.component.annotations.Component";
47 private static final String PROPERTY = "property";
48 private static final String SEP = "|";
49 private static final String UTF_8 = "UTF-8";
Thomas Vachuskabef430b2018-10-22 10:55:47 -070050 private static final String JAVA = ".java";
Thomas Vachuska4a347632018-09-11 11:53:08 -070051 private static final String EXT = ".cfgdef";
52 private static final String STRING = "STRING";
Thomas Vachuskabef430b2018-10-22 10:55:47 -070053 private static final String NO_DESCRIPTION = "no description provided";
Thomas Vachuska4a347632018-09-11 11:53:08 -070054
55 private final File resourceJar;
56 private final JavaProjectBuilder builder;
57
58 private final Map<String, String> constants = Maps.newHashMap();
pierventre13a72352020-10-27 16:08:48 +010059 private final Map<JavaClass, List<String>> pendingProperties = Maps.newHashMap();
Thomas Vachuska4a347632018-09-11 11:53:08 -070060
61 public static void main(String[] args) throws IOException {
62 if (args.length < 2) {
63 System.err.println("usage: cfgdef outputJar javaSource javaSource ...");
64 System.exit(1);
65 }
66 CfgDefGenerator gen = new CfgDefGenerator(args[0], Arrays.copyOfRange(args, 1, args.length));
67 gen.analyze();
68 gen.generate();
69 }
70
71 private CfgDefGenerator(String resourceJarPath, String[] sourceFilePaths) {
72 this.resourceJar = new File(resourceJarPath);
73 this.builder = new JavaProjectBuilder();
74 Arrays.stream(sourceFilePaths).forEach(filename -> {
75 try {
Thomas Vachuskabef430b2018-10-22 10:55:47 -070076 if (filename.endsWith(JAVA))
Thomas Vachuska4a347632018-09-11 11:53:08 -070077 builder.addSource(new File(filename));
Thomas Vachuskabef430b2018-10-22 10:55:47 -070078 } catch (ParseException e) {
79 // When unable to parse, skip the source; leave it to javac to fail.
Thomas Vachuska4a347632018-09-11 11:53:08 -070080 } catch (IOException e) {
81 throw new IllegalArgumentException("Unable to open file", e);
82 }
83 });
84 }
85
86 public void analyze() {
87 builder.getClasses().forEach(this::collectConstants);
88 }
89
90 private void collectConstants(JavaClass javaClass) {
91 javaClass.getFields().stream()
92 .filter(f -> f.isStatic() && f.isFinal() && !f.isPrivate())
93 .forEach(f -> constants.put(f.getName(), f.getInitializationExpression()));
94 }
95
96 public void generate() throws IOException {
97 JarOutputStream jar = new JarOutputStream(new FileOutputStream(resourceJar));
98 for (JavaClass javaClass : builder.getClasses()) {
99 processClass(jar, javaClass);
100 }
101 jar.close();
102 }
103
104 private void processClass(JarOutputStream jar, JavaClass javaClass) throws IOException {
105 Optional<JavaAnnotation> annotation = javaClass.getAnnotations().stream()
106 .filter(ja -> ja.getType().getName().equals(COMPONENT))
107 .findFirst();
108 if (annotation.isPresent()) {
109 AnnotationValue property = annotation.get().getProperty(PROPERTY);
110 List<String> lines = new ArrayList<>();
111 if (property instanceof AnnotationValueList) {
112 AnnotationValueList list = (AnnotationValueList) property;
113 list.getValueList().forEach(v -> processProperty(lines, javaClass, v));
114 } else {
115 processProperty(lines, javaClass, property);
116 }
117
118 if (!lines.isEmpty()) {
pierventre13a72352020-10-27 16:08:48 +0100119 // FIXME this might not work if we have multiple inheritance stages
120 for (JavaClass derivedClass : javaClass.getDerivedClasses()) {
121 // Temporary appends the properties - jar stream cannot be opened multiple times
122 pendingProperties.compute(derivedClass, (k, v) -> {
123 if (v == null) {
124 v = Lists.newArrayList();
125 }
126 v.addAll(lines);
127 return v;
128 });
129 }
130 // Get the attributes stored by the super class
131 List<String> superProperties = pendingProperties.remove(javaClass);
132 if (superProperties != null && !superProperties.isEmpty()) {
133 lines.addAll(superProperties);
134 }
Thomas Vachuska4a347632018-09-11 11:53:08 -0700135 writeCatalog(jar, javaClass, lines);
pierventre13a72352020-10-27 16:08:48 +0100136 } else {
137 // Get the attributes stored by the super class
138 List<String> superProperties = pendingProperties.remove(javaClass);
139 if (superProperties != null && !superProperties.isEmpty()) {
140 writeCatalog(jar, javaClass, superProperties);
141 }
Thomas Vachuska4a347632018-09-11 11:53:08 -0700142 }
143 }
144 }
145
146 private void processProperty(List<String> lines, JavaClass javaClass,
147 AnnotationValue value) {
148 String s = elaborate(value);
149 String pex[] = s.split("=", 2);
150
151 if (pex.length == 2) {
152 String rex[] = pex[0].split(":", 2);
153 String name = rex[0];
154 String type = rex.length == 2 ? rex[1].toUpperCase() : STRING;
155 String def = pex[1];
156 String desc = description(javaClass, name);
157
Thomas Vachuska9b6e45c2018-10-31 15:22:32 -0700158 if (desc != null) {
159 String line = name + SEP + type + SEP + def + SEP + desc + "\n";
160 lines.add(line);
161 }
Thomas Vachuska4a347632018-09-11 11:53:08 -0700162 }
163 }
164
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700165 // Retrieve description from a comment preceding the field named the same
Thomas Vachuska4a347632018-09-11 11:53:08 -0700166 // as the property or
167 // TODO: from an annotated comment.
168 private String description(JavaClass javaClass, String name) {
Ray Milkey5504bd22019-03-22 16:24:38 -0700169 if (name.startsWith("_")) {
170 // Static property - just leave it as is, not for inclusion in the cfg defs
171 return null;
172 }
Thomas Vachuska4a347632018-09-11 11:53:08 -0700173 JavaField field = javaClass.getFieldByName(name);
174 if (field != null) {
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700175 String comment = field.getComment();
176 return comment != null ? comment : NO_DESCRIPTION;
Thomas Vachuska4a347632018-09-11 11:53:08 -0700177 }
pierventre13a72352020-10-27 16:08:48 +0100178 throw new IllegalStateException("cfgdef could not find a variable named " + name + " in "
179 + javaClass.getName());
Thomas Vachuska4a347632018-09-11 11:53:08 -0700180 }
181
182 private String elaborate(AnnotationValue value) {
183 if (value instanceof Add) {
184 return elaborate(((Add) value).getLeft()) + elaborate(((Add) value).getRight());
185 } else if (value instanceof FieldRef) {
Thomas Vachuska0054d342018-10-29 10:19:47 -0700186 return elaborate((FieldRef) value);
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700187 } else if (value != null) {
Thomas Vachuska4a347632018-09-11 11:53:08 -0700188 return stripped(value.toString());
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700189 } else {
190 return "";
Thomas Vachuska4a347632018-09-11 11:53:08 -0700191 }
192 }
193
Thomas Vachuska0054d342018-10-29 10:19:47 -0700194 private String elaborate(FieldRef field) {
195 String name = field.getName();
196 String value = constants.get(name);
197 if (value != null) {
198 return stripped(value);
199 }
200 throw new IllegalStateException("Constant " + name + " cannot be elaborated;" +
201 " value not in the same compilation context");
202 }
203
Thomas Vachuska4a347632018-09-11 11:53:08 -0700204 private String stripped(String s) {
205 return s.trim().replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
206 }
207
208 private void writeCatalog(JarOutputStream jar, JavaClass javaClass, List<String> lines)
209 throws IOException {
210 String name = javaClass.getPackageName().replace('.', '/') + "/" + javaClass.getName() + EXT;
211 jar.putNextEntry(new JarEntry(name));
212 jar.write("# This file is auto-generated\n".getBytes(UTF_8));
213 for (String line : lines) {
214 jar.write(line.getBytes(UTF_8));
215 }
216 jar.closeEntry();
217 }
218
219}