/* * Copyright 2009 Sun Microsystems, Inc. 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 Sun Microsystems, Inc., 4150 Network Circle, Santa Clara, * CA 95054 USA or visit www.sun.com if you need additional information or * have any questions. */ package org.visage.tools.tree.xml; import org.visage.api.tree.ExpressionTree; import org.visage.api.tree.UnitTree; import org.visage.tools.tree.VisageScript; import com.sun.tools.mjavac.util.Context; import com.sun.tools.mjavac.util.Options; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileNotFoundException; import java.io.FileOutputStream; import java.io.IOException; import java.net.MalformedURLException; import java.net.URI; import java.net.URL; import java.util.HashMap; import java.util.Map; import javax.lang.model.element.TypeElement; import javax.xml.transform.OutputKeys; import javax.xml.transform.Result; import javax.xml.transform.Templates; import javax.xml.transform.Transformer; import javax.xml.transform.TransformerFactory; import javax.xml.transform.sax.SAXResult; import javax.xml.transform.sax.SAXTransformerFactory; import javax.xml.transform.sax.TransformerHandler; import javax.xml.transform.stream.StreamResult; import javax.xml.transform.stream.StreamSource; import org.xml.sax.ContentHandler; /** * XML tree transformer hooks after specified phase of the compiler and creates * an XML document that represents abstract syntax tree. The created XML document * is transformed by user specified XSL stylesheet (specified by -XDtreexsl option * from command line). It is possible to specify more than one XSL using a comma * separated values. * * @author A. Sundararajan */ public class TreeXMLTransformer { public static final Context.Key<TreeXMLTransformer> treeXMLTransformerKey = new Context.Key<TreeXMLTransformer>(); public static void preRegister(Context context) { Options options = Options.instance(context); // check if "-XDtreexsl" option is specified if (options.get(TREE_XSL) != null) { try { context.put(treeXMLTransformerKey, new TreeXMLTransformer(context)); } catch (Exception exp) { System.err.println(exp.getMessage()); if (options.get("-doe") != null) { exp.printStackTrace(); } } } } public static void afterParse(Context context, UnitTree cu) { TreeXMLTransformer transformer = context.get(treeXMLTransformerKey); if (transformer != null && transformer.phase == PARSE && (cu instanceof VisageScript)) { transformer.transformWithCare(context, (VisageScript)cu); } } public static void afterEnter(Context context, UnitTree cu, TypeElement clazz) { TreeXMLTransformer transformer = context.get(treeXMLTransformerKey); if (transformer != null && transformer.phase == ENTER && (cu instanceof VisageScript)) { transformer.transformWithCare(context, (VisageScript)cu); } } public static void afterAnalyze(Context context, UnitTree cu, TypeElement clazz) { TreeXMLTransformer transformer = context.get(treeXMLTransformerKey); if (transformer != null && transformer.phase == ANALYZE && (cu instanceof VisageScript)) { transformer.transformWithCare(context, (VisageScript)cu); } } // internals only below this point // print stack trace on exceptions? private boolean printStackOnError; // XSL templates -- could be zero, one or more private Templates[] templatesArray; // output directory in which transformed documents are saved private String outDir; // file extension for output documents private String outExt; // compilation phase after which XSL transformation is applied private int phase; // parameters to the XSL sheets private Map<String, String> transformParams; // various compilation phase values private static final int PARSE = 0x1; private static final int ENTER = 0x2; private static final int ANALYZE = 0x4; // Map to tell whether we are seeing the same compilation unit again private Map<URI, URI> seenCompUnitAlready; // command-line options for tree/xsl feature // XSL stylesheets that will transform AST/XML documents private static final String TREE_XSL = "treexsl"; // output directory to write the transformed documents private static final String TREE_XSL_OUTDIR = "treexsl:d"; // file extension for transformed documents private static final String TREE_XSL_OUTEXT = "treexsl:ext"; // command line option to specify the compilation phase after // which the XSL transformatin is applied private static final String TREE_XSL_PHASE = "treexsl:phase"; // parameters to XSL. private static final String TREE_XSL_PARAMS = "treexsl:params"; // the default values of command-line options // default output directory where transformed source is saved private static final String DEFAULT_OUTDIR = "treexsl"; // default output document extension is ".xml" private static final String DEFAULT_OUTEXT = "xml"; // by default, XSL transform is applied after "analyze" phase // i.e., after parse-enter-analyze => types and symbols are // available whereever applicable private static final int DEFAULT_PHASE = ANALYZE; private TreeXMLTransformer(Context context) { seenCompUnitAlready = new HashMap<URI, URI>(); printStackOnError = Options.instance(context).get("-doe") != null; initTemplatesArray(context); initOutputDirectory(context); initOutputExtension(context); initTransformPhase(context); initTransformParams(context); } private void initTemplatesArray(Context context) { Options options = Options.instance(context); String xsl = options.get(TREE_XSL); try { if (xsl != null && xsl.length() != 0 && !xsl.equals(TREE_XSL)) { TransformerFactory fac = TransformerFactory.newInstance(); String[] files = xsl.split(","); templatesArray = new Templates[files.length]; for (int i = 0; i < files.length; i++) { File file = new File(files[i]); if (!file.exists()) { // try URL URL url; try { url = new URL(files[i]); templatesArray[i] = fac.newTemplates(new StreamSource(url.openStream())); } catch (MalformedURLException mue) { throw new IllegalArgumentException("File not found: " + file); } } else { templatesArray[i] = fac.newTemplates(new StreamSource(file)); } } } else { this.templatesArray = new Templates[0]; } } catch (Exception exp) { throw wrapException(exp); } } private void initOutputDirectory(Context context) { Options options = Options.instance(context); outDir = options.get(TREE_XSL_OUTDIR); if (outDir == null) { outDir = DEFAULT_OUTDIR; } File file = new File(outDir); if (!file.exists() && !file.mkdir()) { throw new IllegalArgumentException("Directory not found: " + outDir); } if (!file.isDirectory()) { throw new IllegalArgumentException("Not a directory: " + outDir); } } private void initOutputExtension(Context context) { Options options = Options.instance(context); String ext = options.get(TREE_XSL_OUTEXT); if (ext == null) { ext = DEFAULT_OUTEXT; } outExt = "." + ext; } private void initTransformPhase(Context context) { Options options = Options.instance(context); String phaseName = options.get(TREE_XSL_PHASE); if (phaseName == null) { phase = DEFAULT_PHASE; } else if (phaseName.equals("parse")) { phase = PARSE; } else if (phaseName.equals("enter")) { phase = ENTER; } else if (phaseName.equals("analyze")) { phase = ANALYZE; } else { throw new IllegalArgumentException("Invalid phase: " + phaseName); } } private void initTransformParams(Context context) { this.transformParams = new HashMap<String, String>(); Options options = Options.instance(context); String paramsOption = options.get(TREE_XSL_PARAMS); try { if (paramsOption != null) { String[] params = paramsOption.split(","); for (String p : params) { int index = p.lastIndexOf('='); String key = (index == -1) ? p : p.substring(0, index); String value = (index == -1) ? "" : p.substring(index + 1); transformParams.put(key, value); } } } catch (Exception exp) { throw wrapException(exp); } } private void transformWithCare(Context context, VisageScript script) { try { transform(context, script); } catch (Exception exp) { System.err.println(exp.getMessage()); if (printStackOnError) { exp.printStackTrace(); } } } // transform given compilation unit using XSL private void transform(Context context, VisageScript script) { URI uri = script.getSourceFile().toUri(); if (seenCompUnitAlready.containsKey(uri)) { return; } seenCompUnitAlready.put(uri, uri); ByteArrayOutputStream bos = new ByteArrayOutputStream(); StreamResult streamResult = new StreamResult(bos); TransformerHandler handler = createXSLChain(streamResult); try { // convert AST-to-XML convertAST2XML(context, script, handler); // collect the transformed XML output into buffer bos.flush(); byte[] buf = bos.toByteArray(); bos.close(); // make package directory as needed and get source file name String outFile = getOutputFileName(script); writeOutput(buf, outFile); } catch (Exception exp) { throw wrapException(exp); } } private TransformerHandler createXSLChain(Result finalResult) { SAXTransformerFactory fac = (SAXTransformerFactory) TransformerFactory.newInstance(); TransformerHandler handler; try { Templates[] tempArray = templatesArray; if (tempArray.length != 0) { TransformerHandler[] handlers = new TransformerHandler[tempArray.length]; for (int i = 0; i < tempArray.length; i++) { handlers[i] = fac.newTransformerHandler(tempArray[i]); } // connect the chain elements... for (int i = 0; i < handlers.length - 1; i++) { handlers[i].setResult(new SAXResult(handlers[i + 1])); } // last element of the chain gives output to the final result. TransformerHandler lastHandler = handlers[handlers.length - 1]; lastHandler.setResult(finalResult); Transformer transformer = lastHandler.getTransformer(); setTransformerOptions(transformer); handler = handlers[0]; } else { handler = fac.newTransformerHandler(); Transformer transformer = handler.getTransformer(); setTransformerOptions(transformer); handler.setResult(finalResult); } } catch (Exception exp) { throw wrapException(exp); } return handler; } private void setTransformerOptions(Transformer transformer) { transformer.setOutputProperty(OutputKeys.ENCODING, "ISO-8859-1"); transformer.setOutputProperty(OutputKeys.INDENT, "yes"); for (Map.Entry<String, String> p : transformParams.entrySet()) { transformer.setParameter(p.getKey(), p.getValue()); } } // converts AST into XML (SAX) events private void convertAST2XML(Context context, VisageScript script, ContentHandler handler) { try { TreeXMLSerializer visitor = new TreeXMLSerializer(handler); Compiler.enter(context, script, visitor); visitor.start(script); } finally { Compiler.leave(); } } // writes transformed document private void writeOutput(byte[] buf, String outFile) throws IOException, FileNotFoundException { outFile = outFile.replace(".visage", outExt); FileOutputStream fos = new FileOutputStream(outFile); try { fos.write(buf); } finally { fos.close(); } } private String getOutputFileName(UnitTree cu) { String pkgName = null; ExpressionTree pkg = cu.getPackageName(); if (pkg != null) { pkgName = pkg.toString().replace('.', File.separatorChar); } StringBuilder sbuf = new StringBuilder(outDir); sbuf.append(File.separatorChar); if (pkgName != null) { sbuf.append(pkgName); sbuf.append(File.separatorChar); // create package subdirs under output directory new File(sbuf.toString()).mkdirs(); } sbuf.append(cu.getSourceFile().getName()); return sbuf.toString(); } private static RuntimeException wrapException(Exception exp) { if (exp instanceof RuntimeException) { return (RuntimeException) exp; } else { return new RuntimeException(exp); } } }