blob: 6fd733169c2404a2e6e294a0e7ad29cf40e42864 [file] [log] [blame]
/*
* 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 com.thoughtworks.qdox.parser.ParseException;
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 JAVA = ".java";
private static final String EXT = ".cfgdef";
private static final String STRING = "STRING";
private static final String NO_DESCRIPTION = "no description provided";
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 {
if (filename.endsWith(JAVA))
builder.addSource(new File(filename));
} catch (ParseException e) {
// When unable to parse, skip the source; leave it to javac to fail.
} 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);
if (desc != null) {
String line = name + SEP + type + SEP + def + SEP + desc + "\n";
lines.add(line);
}
}
}
// Retrieve description from a comment preceding the field named the same
// as the property or
// TODO: from an annotated comment.
private String description(JavaClass javaClass, String name) {
if (name.startsWith("_")) {
// Static property - just leave it as is, not for inclusion in the cfg defs
return null;
}
JavaField field = javaClass.getFieldByName(name);
if (field != null) {
String comment = field.getComment();
return comment != null ? comment : NO_DESCRIPTION;
}
throw new IllegalStateException("cfgdef could not find a variable named " + name + " in " + javaClass.getName());
}
private String elaborate(AnnotationValue value) {
if (value instanceof Add) {
return elaborate(((Add) value).getLeft()) + elaborate(((Add) value).getRight());
} else if (value instanceof FieldRef) {
return elaborate((FieldRef) value);
} else if (value != null) {
return stripped(value.toString());
} else {
return "";
}
}
private String elaborate(FieldRef field) {
String name = field.getName();
String value = constants.get(name);
if (value != null) {
return stripped(value);
}
throw new IllegalStateException("Constant " + name + " cannot be elaborated;" +
" value not in the same compilation context");
}
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();
}
}