blob: a0fcd71804f772a81c8c441f8b607a85a2e14723 [file] [log] [blame]
/*
* Copyright (c) 2015, 2016, Oracle and/or its affiliates. All rights reserved.
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
*
* This code is free software; you can redistribute it and/or modify it
* under the terms of the GNU General Public License version 2 only, as
* published by the Free Software Foundation.
*
* This code is distributed in the hope that it will be useful, but WITHOUT
* ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
* FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
* version 2 for more details (a copy is included in the LICENSE file that
* accompanied this code).
*
* You should have received a copy of the GNU General Public License version
* 2 along with this work; if not, write to the Free Software Foundation,
* Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
*
* Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
* or visit www.oracle.com if you need additional information or have any
* questions.
*/
package sampleapi.generator;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Map;
import javax.xml.parsers.DocumentBuilderFactory;
import org.w3c.dom.Element;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import com.sun.source.util.JavacTask;
import com.sun.tools.javac.api.JavacTool;
import com.sun.tools.javac.api.JavacTaskImpl;
import com.sun.tools.javac.util.Context;
import com.sun.tools.javac.util.Name;
import com.sun.tools.javac.util.Names;
import com.sun.tools.javac.util.List;
import com.sun.tools.javac.util.ListBuffer;
import com.sun.tools.javac.tree.TreeMaker;
import com.sun.tools.javac.tree.JCTree;
import com.sun.tools.javac.tree.JCTree.*;
import com.sun.tools.javac.code.Flags;
import com.sun.tools.javac.code.Type;
import com.sun.tools.javac.code.TypeTag;
import com.sun.tools.javac.code.Symbol;
import com.sun.tools.javac.code.Symtab;
import java.nio.file.Path;
import sampleapi.util.*;
public class PackageGenerator {
public String packageName;
String packageDirName;
public String id;
ArrayList<JCCompilationUnit> topLevels;
Map<String, Integer> nameIndex;
public Map<String, JCClassDecl> idBases;
Map<String, JCAnnotation> idAnnos;
TreeMaker make;
Names names;
Symtab syms;
DocumentBuilderFactory factory;
Documentifier documentifier;
boolean fx;
public PackageGenerator() {
JavacTool jt = JavacTool.create();
JavacTask task = jt.getTask(null, null, null, null, null, null);
Context ctx = ((JavacTaskImpl)task).getContext();
make = TreeMaker.instance(ctx);
names = Names.instance(ctx);
syms = Symtab.instance(ctx);
factory = DocumentBuilderFactory.newInstance();
documentifier = Documentifier.instance(ctx);
}
boolean isDataSetProcessed = false;
public static PackageGenerator processDataSet(Element rootElement) {
PackageGenerator result = new PackageGenerator();
result.isDataSetProcessed = true;
result.topLevels = new ArrayList<>();
result.nameIndex = new HashMap<>();
result.idBases = new HashMap<>();
result.idAnnos = new HashMap<>();
result.fx = false;
if (!rootElement.getTagName().equals("package")) {
throw new IllegalStateException("Unexpected tag name: "
+ rootElement.getTagName());
}
result.packageName = rootElement.getAttribute("name");
result.id = rootElement.getAttribute("id");
result.fx = "fx".equals(rootElement.getAttribute("style"));
result.packageDirName = result.packageName.replace('.', '/');
// process nodes (toplevels)
NodeList nodeList = rootElement.getChildNodes();
for (int i = 0; i < nodeList.getLength(); i++) {
Node node = nodeList.item(i);
if (!(node instanceof Element)) {
continue;
}
result.processTopLevel((Element) node);
}
return result;
}
public void generate(Path outDir) {
if (!isDataSetProcessed)
throw new RuntimeException("No Data Set processed");
try {
File pkgDir = new File(outDir.toFile(), packageDirName);
pkgDir.mkdirs();
for (JCCompilationUnit decl : topLevels) {
JCClassDecl classDecl = (JCClassDecl) decl.getTypeDecls().get(0);
File outFile
= new File(pkgDir, classDecl.getSimpleName().toString() + ".java");
FileWriter writer = new FileWriter(outFile);
writer.write(decl.toString());
writer.flush();
writer.close();
}
// package-info
File outFile = new File(pkgDir, "package-info.java");
FileWriter writer = new FileWriter(outFile);
writer.write("/**\n");
writer.write(documentifier.getDocGenerator().getPackageComment());
writer.write("*/\n");
writer.write("package " + packageName + ";\n");
writer.flush();
writer.close();
// overview
outFile = new File(pkgDir, "overview.html");
writer = new FileWriter(outFile);
writer.write("<html>\n");
writer.write("<head>\n<title>" + packageName + "</title>\n</head>\n");
writer.write("<body>\n");
writer.write("<p>Package " + packageName + " overview.\n");
writer.write("</body>\n");
writer.write("</html>\n");
writer.flush();
writer.close();
} catch (IOException e) {
throw new RuntimeException("Error writing output");
}
}
// levels' processing methods
void processTopLevel(Element tlTag) {
String kind = tlTag.getTagName();
if (kind.equals("annodecl")) {
// decls stored separately, does not affect bases
String declId = tlTag.getAttribute("id");
if (!declId.startsWith("@"))
declId = "@" + declId;
idAnnos.put(declId, processAnnoDecl(tlTag));
return;
}
ListBuffer<JCTree>[] bases = processBases(tlTag, null);
for (JCTree base : bases[0]) { // [0] - bases namely
JCPackageDecl pkg = make.PackageDecl(
List.<JCAnnotation>nil(),
make.QualIdent(
new Symbol.PackageSymbol(
names.fromString(packageName),
null)));
ListBuffer<JCTree> topLevelParts = new ListBuffer<>();
topLevelParts.append(pkg);
topLevelParts.appendList(bases[1]); // [1] imports
topLevelParts.append(base);
JCCompilationUnit topLevel = make.TopLevel(topLevelParts.toList());
documentifier.documentify(topLevel, fx);
topLevels.add(topLevel);
}
}
ListBuffer<JCTree>[] processBases(Element baseTag, HashMap<String, Integer> scope) {
String kind = baseTag.getTagName();
String baseName = baseTag.getAttribute("basename");
String typeParam = baseTag.getAttribute("tparam");
String baseId = baseTag.getAttribute("id");
System.out.println("Found class id: " + baseId);
long kindFlag = 0;
switch (kind) {
case "class":
// no flags for class
break;
case "interface":
kindFlag |= Flags.INTERFACE;
break;
case "enum":
kindFlag |= Flags.ENUM;
break;
case "annotation":
kindFlag |= Flags.ANNOTATION | Flags.INTERFACE;
break;
}
// go through other nodes; add modifiers to multiplier
NodeList nodes = baseTag.getChildNodes();
ListBuffer<JCTree> bases = new ListBuffer<>();
ListBuffer<JCTree> members = new ListBuffer<>();
ListBuffer<JCTree> imports = new ListBuffer<>();
JCExpression extType = null;
ListBuffer<JCExpression> implTypes = new ListBuffer<>();
SimpleMultiplier multiply = new SimpleMultiplier();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (!(node instanceof Element))
continue;
Element element = (Element)node;
switch (element.getTagName()) {
case "modifier":
multiply.addAxis(element.getTextContent());
break;
case "anno":
multiply.addAxis(element.getTextContent());
break;
case "member":
// process members here
members.appendList(processMembers(element, baseName, kind));
break;
case "extend":
String classId = element.getAttribute("id"); // this pkg
String classRef = element.getAttribute("ref"); // external
if (classId.length() !=0 &&
idBases.containsKey(classId)) {
// if have base, take methods from base members
JCClassDecl baseDecl = idBases.get(classId);
extType = make.Type(
getTypeByName(
baseDecl.getSimpleName().toString()));
members.appendList(processMethods(baseDecl.getMembers(), false));
} else if (classRef.length() !=0) {
extType = make.Type(getTypeByName(classRef));
}
break;
case "implement":
String interfaceId = element.getAttribute("id");
String interfaceRef = element.getAttribute("ref");
if (interfaceId.length() != 0 &&
idBases.containsKey(interfaceId)) {
JCClassDecl baseDecl = idBases.get(interfaceId);
implTypes.add(
make.Type(
getTypeByName(
baseDecl.getSimpleName().toString())));
members.appendList(processMethods(baseDecl.getMembers(), true));
} else if (interfaceRef.length() != 0) {
implTypes.add(make.Type(getTypeByName(interfaceRef)));
}
break;
case "import":
imports.append(
make.Import(
make.Ident(names.fromString(element.getTextContent())),
false));
}
}
// process modifiers through multiplier
multiply.initIterator();
while (multiply.hasNext()) {
ArrayList<String> tuple = multiply.getNext();
long declFlags = kindFlag;
ListBuffer<JCAnnotation> annos = new ListBuffer<>();
for (String modifier : tuple) {
if (modifier.startsWith("@") && idAnnos.containsKey(modifier))
annos.add(idAnnos.get(modifier)); // it's anno
else
declFlags |= getFlagByName(modifier); // it's modifier
}
String declName = (scope == null)
? getUniqName(baseName)
: baseName + getUniqIndex(scope, baseName);
JCClassDecl baseDecl = make.ClassDef(
make.Modifiers(declFlags, annos.toList()),
names.fromString(declName),
processTypeParams(typeParam), // type params
extType, // ext
implTypes.toList(), // impl
members.toList()); // members
// fix constructors names
fixConstructorNames(baseDecl);
bases.append(baseDecl);
// for non-empty ids store first base occurence from multiplied sequence
if (baseId.length() != 0) {
idBases.put(baseId, baseDecl);
baseId = "";
}
}
return new ListBuffer[] { bases, imports };
}
List<JCTypeParameter> processTypeParams(String typeParams) {
if (typeParams == null || typeParams.length() == 0)
return List.<JCTypeParameter>nil(); // empty
String[] typeVarsArr = typeParams.split(",");
ListBuffer<JCTypeParameter> typeParamsDecls = new ListBuffer<>();
for (String typeVar : typeVarsArr) {
typeParamsDecls.add(
make.TypeParameter(names.fromString(typeVar),
List.<JCExpression>nil()));
}
return typeParamsDecls.toList();
}
ListBuffer<JCTree> processMembers(Element memberTag, String name, String kind) {
ListBuffer<JCTree> members = new ListBuffer<>();
NodeList nodes = memberTag.getChildNodes();
HashMap<String, Integer> scope = new HashMap<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (!(node instanceof Element))
continue;
switch (((Element)node).getTagName()) {
case "field":
members.appendList(processFields((Element)node, scope));
break;
case "serialfield":
members.append(processSerialFields((Element)node));
break;
case "constant":
members.appendList(processConstants((Element)node, scope));
break;
case "constructor":
members.appendList(processMethods((Element)node, scope, true, true));
break;
case "method":
boolean needBody = kind.equals("class") || kind.equals("enum");
members.appendList(processMethods((Element)node, scope, needBody, false));
break;
case "class":
case "interface":
case "enum":
case "annotation":
members.appendList(processBases((Element)node, scope)[0]);
break;
}
}
return members;
}
ListBuffer<JCTree> processFields(Element fieldsNode, HashMap<String, Integer> scope) {
String kind = fieldsNode.getTagName();
String baseName = fieldsNode.getAttribute("basename");
ListBuffer<JCTree> fields = new ListBuffer<>();
NodeList nodes = fieldsNode.getChildNodes();
SimpleMultiplier multiply = new SimpleMultiplier(); // for modifiers
String[] types = new String[] {};
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (!(node instanceof Element))
continue;
// parse type and modifiers
switch (((Element)node).getTagName()) {
case "modifier":
multiply.addAxis(((Element)node).getTextContent());
break;
case "anno":
multiply.addAxis(((Element)node).getTextContent());
case "type":
types = ((Element)node).getTextContent().split("\\|");
break;
}
}
// process through modifiers and types
multiply.initIterator();
while (multiply.hasNext()) {
ArrayList<String> tuple = multiply.getNext();
long declFlags = 0;
ListBuffer<JCAnnotation> annos = new ListBuffer<>();
for (String modifier : tuple) {
if (modifier.startsWith("@") && idAnnos.containsKey(modifier))
annos.add(idAnnos.get(modifier)); // it's anno
else
declFlags |= getFlagByName(modifier); // it's modifier
}
for (String type : types) {
String declName = baseName + getUniqIndex(scope, baseName);
Type initType = getTypeByName(type);
JCExpression initExpr = null;
if ((declFlags & Flags.STATIC) != 0) // static to be initialized
initExpr = make.Literal(initType.isPrimitive() ?
initType.getTag() :
TypeTag.BOT,
"String".equals(type)
? new String("blah-blah-blah")
: Integer.valueOf(0));
JCVariableDecl fieldDecl = make.VarDef(
make.Modifiers(declFlags, annos.toList()),
names.fromString(declName),
make.Type(getTypeByName(type)),
initExpr);
fields.append(fieldDecl);
}
}
return fields;
}
JCTree processSerialFields(Element sfNode) {
String baseName = sfNode.getAttribute("basename");
String[] fieldTypes = sfNode.getTextContent().split(",");
ListBuffer<JCExpression> serialFields = new ListBuffer<>();
HashMap<String, Integer> scope = new HashMap<>();
for (String fType : fieldTypes) {
String fieldName = baseName + getUniqIndex(scope, baseName);
serialFields.add(
make.NewClass(
null,
null,
make.Type(getTypeByName("ObjectStreamField")),
List.from(
new JCTree.JCExpression[] {
make.Literal(fieldName),
make.Ident(names.fromString(fType + ".class"))
}),
null));
}
JCTree sfDecl = make.VarDef(
make.Modifiers(
Flags.PRIVATE | Flags.STATIC | Flags.FINAL),
names.fromString("serialPersistentFields"),
make.TypeArray(
make.Type(getTypeByName("ObjectStreamField"))),
make.NewArray(
null,
List.<JCExpression>nil(),
serialFields.toList()));
return sfDecl;
}
ListBuffer<JCTree> processConstants(Element constNode, HashMap<String, Integer> scope) {
String baseName = constNode.getAttribute("basename");
int count = 1;
try {
count = Integer.parseInt(constNode.getAttribute("count"));
} catch (Exception e) {} // nothing to do, will use count = 1
long declFlags = Flags.PUBLIC | Flags.STATIC | Flags.FINAL | Flags.ENUM;
ListBuffer<JCTree> fields = new ListBuffer<>();
for (int i = 0; i < count; i++) {
String declName = baseName +
((count == 1) ? "" : getUniqIndex(scope, baseName));
JCVariableDecl constDecl = make.VarDef(
make.Modifiers(declFlags),
names.fromString(declName),
null, // no need for type in enum decl
null); // no init
fields.append(constDecl);
}
return fields;
}
ListBuffer<JCTree> processMethods(Element methodsNode, HashMap<String, Integer> scope, boolean needBody, boolean isConstructor) {
String kind = methodsNode.getTagName();
String baseName = methodsNode.getAttribute("basename");
String name = methodsNode.getAttribute("name");
String methodTypeParam = methodsNode.getAttribute("tparam");
ListBuffer<JCTree> methods = new ListBuffer<>();
NodeList nodes = methodsNode.getChildNodes();
SimpleMultiplier multiply = new SimpleMultiplier(); // for modifiers
String[] types = new String[0];
String[] params = new String[] { "none" }; // default - no params
ListBuffer<Type> throwTypes = new ListBuffer<>();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (!(node instanceof Element))
continue;
// parse type and modifiers
switch (((Element)node).getTagName()) {
case "modifier":
multiply.addAxis(((Element)node).getTextContent());
break;
case "anno":
multiply.addAxis(((Element)node).getTextContent());
break;
case "type":
types = ((Element)node).getTextContent().split("\\|");
break;
case "param":
params = ((Element)node).getTextContent().split("\\|");
break;
case "throw":
throwTypes.add(
getTypeByName(((Element)node).getTextContent()));
break;
}
}
// constructor?
if (isConstructor) {
baseName = "constructor";
types = new String[] { "" };
}
// direct name not indexed
boolean isDirectName = false;
if (name.length() > 0) {
baseName = name;
isDirectName = true;
}
// process through modifiers and types
multiply.initIterator();
while (multiply.hasNext()) {
ArrayList<String> tuple = multiply.getNext();
long declFlags = 0;
ListBuffer<JCAnnotation> annos = new ListBuffer<>();
for (String modifier : tuple) {
if (modifier.startsWith("@") && idAnnos.containsKey(modifier))
annos.add(idAnnos.get(modifier)); // it's anno
else
declFlags |= getFlagByName(modifier); // it's modifier
}
for (String type : types) {
String declName = baseName
+ ((isConstructor || isDirectName)
? "" : getUniqIndex(scope, baseName));
JCBlock body = null;
if (needBody && (declFlags & Flags.ABSTRACT) == 0) { // create body
List<JCStatement> bodyStatements = List.<JCStatement>nil();
if (!type.equals("") && !type.equals("void")) { // create return statement
Type retType = getTypeByName(type);
bodyStatements = List.<JCStatement>of(
make.Return(
make.Literal(
retType.isPrimitive() ?
retType.getTag() :
TypeTag.BOT,
Integer.valueOf(0))));
}
body = make.Block(0, bodyStatements);
}
// same method by different params (if they exist)
for (String param : params) {
JCMethodDecl methodDecl =
make.MethodDef(
make.Modifiers(declFlags, annos.toList()),
names.fromString(declName),
isConstructor ? null : make.Type(getTypeByName(type)),
processTypeParams(methodTypeParam), // type params
null, // no receiver
processParams(param), // formal params
make.Types(throwTypes.toList()), // throws
body,
null); // no default value YET
methods.append(methodDecl);
}
}
}
return methods;
}
JCAnnotation processAnnoDecl(Element annoDeclNode) {
String annoId = annoDeclNode.getAttribute("id");
ListBuffer<JCExpression> args = new ListBuffer<>();
String className = "";
NodeList nodes = annoDeclNode.getChildNodes();
for (int i = 0; i < nodes.getLength(); i++) {
Node node = nodes.item(i);
if (!(node instanceof Element))
continue;
switch (((Element)node).getTagName()) {
case "class":
className = ((Element)node).getTextContent();
break;
case "arg":
String argName = ((Element)node).getAttribute("name");
String argValue = ((Element)node).getAttribute("value");
JCExpression arg;
if (argName.length() == 0)
arg = make.Ident(names.fromString(argValue));
else
arg = make.Assign(
make.Ident(names.fromString(argName)),
make.Ident(names.fromString(argValue)));
args.add(arg);
break;
}
}
return make.Annotation(
make.Ident(names.fromString(className)),
args.toList());
}
ListBuffer<JCTree> processMethods(List<JCTree> tree, boolean needBody) {
// for "extends" clause; returns methods only
ListBuffer<JCTree> methods = new ListBuffer<>();
for (JCTree memberDecl : tree) {
if (memberDecl instanceof JCMethodDecl) {
JCMethodDecl methodDecl = (JCMethodDecl)memberDecl;
JCTree retTypeTree = methodDecl.getReturnType();
// skip constructors
if (retTypeTree == null)
continue;
if (needBody) {
// here we need to 'implement' interface declared methods
Type retType = retTypeTree.type;
List<JCStatement> bodyStatements = List.<JCStatement>nil();
if (retType.getTag() != TypeTag.VOID)
bodyStatements = List.<JCStatement>of(
make.Return(
make.Literal(
retType.isPrimitive() ?
retType.getTag() :
TypeTag.BOT,
Integer.valueOf(0))));
JCBlock body = make.Block(0, bodyStatements);
methodDecl = make.MethodDef(
methodDecl.getModifiers(),
methodDecl.getName(),
(JCExpression)methodDecl.getReturnType(),
methodDecl.getTypeParameters(),
methodDecl.getReceiverParameter(),
methodDecl.getParameters(),
methodDecl.getThrows(),
body,
(JCExpression)methodDecl.getDefaultValue());
}
methods.add(methodDecl);
}
}
return methods;
}
void fixConstructorNames(JCClassDecl baseDecl) {
ListBuffer<JCTree> newMembers = new ListBuffer<>();
List<JCTree> members = baseDecl.getMembers();
Name name = baseDecl.getSimpleName();
for (JCTree memberDecl : members) {
JCTree newDecl = memberDecl;
if (memberDecl instanceof JCMethodDecl) {
JCMethodDecl methodDecl = (JCMethodDecl)memberDecl;
JCTree retTypeTree = methodDecl.getReturnType();
if (retTypeTree == null)
newDecl = make.MethodDef(
methodDecl.getModifiers(),
name,
(JCExpression)methodDecl.getReturnType(),
methodDecl.getTypeParameters(),
methodDecl.getReceiverParameter(),
methodDecl.getParameters(),
methodDecl.getThrows(),
methodDecl.getBody(),
(JCExpression)methodDecl.getDefaultValue());
}
newMembers.add(newDecl);
}
baseDecl.defs = newMembers.toList();
}
List<JCVariableDecl> processParams(String paramTypes) {
if ("none".equals(paramTypes))
return List.<JCVariableDecl>nil(); // empty
String[] typesArr = paramTypes.split(",(?!(\\w+,)*\\w+>)");
ListBuffer<JCVariableDecl> paramsDecls = new ListBuffer<>();
int i = 0;
for (String typeName : typesArr) {
String paramName = "param"
+ (typesArr.length == 1 ? "" : String.valueOf(i));
paramsDecls.add(
make.VarDef(make.Modifiers(0),
names.fromString(paramName),
make.Type(getTypeByName(typeName)),
null));
i++;
}
return paramsDecls.toList();
}
//
// util methods
//
String getUniqName(String name) {
if (!nameIndex.containsKey(name))
nameIndex.put(name, 0);
Integer index = nameIndex.get(name);
String uniqName = name + index;
nameIndex.put(name, index + 1);
return uniqName;
}
int getUniqIndex(HashMap<String, Integer> scope, String name) {
if (!scope.containsKey(name))
scope.put(name, 0);
Integer index = scope.get(name);
scope.put(name, index + 1);
return index;
}
long getFlagByName(String modifierName) {
switch (modifierName) {
case "public":
return Flags.PUBLIC;
case "private":
return Flags.PRIVATE;
case "protected":
return Flags.PROTECTED;
case "static":
return Flags.STATIC;
case "final":
return Flags.FINAL;
case "abstract":
return Flags.ABSTRACT;
case "strictfp":
return Flags.STRICTFP;
default:
return 0;
}
}
Type getTypeByName(String typeName) {
//check for primitive types
switch (typeName) {
case "void":
return syms.voidType;
case "boolean":
return syms.booleanType;
case "byte":
return syms.byteType;
case "char":
return syms.charType;
case "double":
return syms.doubleType;
case "float":
return syms.floatType;
case "int":
return syms.intType;
case "long":
return syms.longType;
default:
return getTypeByName(typeName, List.<Type>nil());
}
}
Type getTypeByName(String typeName, List<Type> tparams) {
return new Type.ClassType(
Type.noType,
tparams,
new Symbol.ClassSymbol(0, names.fromString(typeName), null));
}
}