blob: 94882617923f8d3007c5e79e27b62db7f9a606b1 [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
134 String line = name + SEP + type + SEP + def + SEP + desc + "\n";
135 lines.add(line);
Thomas Vachuska4a347632018-09-11 11:53:08 -0700136 }
137 }
138
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700139 // Retrieve description from a comment preceding the field named the same
Thomas Vachuska4a347632018-09-11 11:53:08 -0700140 // as the property or
141 // TODO: from an annotated comment.
142 private String description(JavaClass javaClass, String name) {
143 JavaField field = javaClass.getFieldByName(name);
144 if (field != null) {
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700145 String comment = field.getComment();
146 return comment != null ? comment : NO_DESCRIPTION;
Thomas Vachuska4a347632018-09-11 11:53:08 -0700147 }
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700148 return NO_DESCRIPTION;
Thomas Vachuska4a347632018-09-11 11:53:08 -0700149 }
150
151 private String elaborate(AnnotationValue value) {
152 if (value instanceof Add) {
153 return elaborate(((Add) value).getLeft()) + elaborate(((Add) value).getRight());
154 } else if (value instanceof FieldRef) {
Thomas Vachuska0054d342018-10-29 10:19:47 -0700155 return elaborate((FieldRef) value);
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700156 } else if (value != null) {
Thomas Vachuska4a347632018-09-11 11:53:08 -0700157 return stripped(value.toString());
Thomas Vachuskabef430b2018-10-22 10:55:47 -0700158 } else {
159 return "";
Thomas Vachuska4a347632018-09-11 11:53:08 -0700160 }
161 }
162
Thomas Vachuska0054d342018-10-29 10:19:47 -0700163 private String elaborate(FieldRef field) {
164 String name = field.getName();
165 String value = constants.get(name);
166 if (value != null) {
167 return stripped(value);
168 }
169 throw new IllegalStateException("Constant " + name + " cannot be elaborated;" +
170 " value not in the same compilation context");
171 }
172
Thomas Vachuska4a347632018-09-11 11:53:08 -0700173 private String stripped(String s) {
174 return s.trim().replaceFirst("^[^\"]*\"", "").replaceFirst("\"$", "");
175 }
176
177 private void writeCatalog(JarOutputStream jar, JavaClass javaClass, List<String> lines)
178 throws IOException {
179 String name = javaClass.getPackageName().replace('.', '/') + "/" + javaClass.getName() + EXT;
180 jar.putNextEntry(new JarEntry(name));
181 jar.write("# This file is auto-generated\n".getBytes(UTF_8));
182 for (String line : lines) {
183 jar.write(line.getBytes(UTF_8));
184 }
185 jar.closeEntry();
186 }
187
188}