/* * 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.InputStream; import java.io.FileInputStream; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.ParserConfigurationException; import org.xml.sax.SAXException; import org.w3c.dom.Document; 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 sampleapi.util.*; import sampleapi.SampleApi.Fault; public class PackageGenerator { String packageName; String packageDirName; ArrayList<JCCompilationUnit> topLevels; Map<String, Integer> nameIndex; 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 void processDataSet(File dsFile) throws Fault { isDataSetProcessed = true; topLevels = new ArrayList<>(); nameIndex = new HashMap<>(); idBases = new HashMap<>(); idAnnos = new HashMap<>(); fx = false; try { InputStream is = new FileInputStream(dsFile); DocumentBuilder builder = factory.newDocumentBuilder(); Document document = builder.parse(is); Element rootElement = document.getDocumentElement(); if (!rootElement.getTagName().equals("package")) throw new IllegalStateException("Unexpected tag name: " + rootElement.getTagName()); packageName = rootElement.getAttribute("name"); fx = "fx".equals(rootElement.getAttribute("style")); packageDirName = 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; processTopLevel((Element) node); } } catch (ParserConfigurationException | SAXException | IOException e) { throw new Fault("Error parsing dataset " + dsFile, e); } } public void generate(File outDir) throws Fault { if (!isDataSetProcessed) throw new Fault("No Data Set processed"); try { File pkgDir = new File(outDir, 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 Fault("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"); 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)); } }