/* * Rapid Beans Framework, SDK, Ant Tasks: TaskGenModel.java * * Copyright (C) 2009 Martin Bluemel * * Creation Date: 07/01/2005 * * This program is free software; you can redistribute it and/or modify it under the terms of the * GNU Lesser General Public License as published by the Free Software Foundation; * either version 3 of the License, or (at your option) any later version. * This program 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 Lesser General Public License for more details. * You should have received a copies of the GNU Lesser General Public License and the * GNU General Public License along with this program; if not, see <http://www.gnu.org/licenses/>. */ package org.rapidbeans.ant; import java.io.File; import java.io.FileInputStream; import java.io.IOException; import javax.xml.parsers.DocumentBuilder; import javax.xml.parsers.DocumentBuilderFactory; import javax.xml.parsers.ParserConfigurationException; import org.apache.tools.ant.BuildException; import org.apache.tools.ant.Project; import org.apache.tools.ant.Task; import org.apache.tools.ant.taskdefs.XSLTProcess; import org.w3c.dom.Document; import org.w3c.dom.Node; import org.xml.sax.SAXException; /** * This Ant task conveniently drives the XSLT based RapidBeans code generation * for a whole directory hierarchy (similar to Ant task "javac"). * * @author Martin Bluemel */ public final class TaskGenModel extends Task { /** * force flag. */ private boolean force = false; /** * the model model description's directory. */ private File srcdir = null; /** * the generated source's directory. */ private File destdirsimple = null; /** * the generated source's directory. */ private File destdirjoint = null; /** * the directory with the code generation templates. */ private File styledir = null; /** * the code generation template for Enums. */ private File styleEnum; /** * the code generation template for Quantities. */ private File styleQuantity; /** * the code generation template for Rapid Beans. */ private File styleBean; /** * the code generation mode. Overwrites the mode particularly specified in * the model. */ private CodeGenMode codeGenMode = null; private static final CodeGenMode DEFAULT_CODEGEN_MODE = CodeGenMode.simple; /** * the code generation implementation: { 'simple' | 'strict' }. Overwrites * the implementation particularly specified in the model. */ private CodeGenImpl codeGenImpl = null; private static final CodeGenImpl DEFAULT_CODEGEN_IMPL = CodeGenImpl.simple; private String indent = "\t"; /** * set the force flag which determines if the generation should be performed * not regarding modification dates. * * @param force * determines if the generation should be performed not regarding * modification dates */ public void setForce(final boolean force) { this.force = force; } /** * set the model description's directory. * * @param srcdir * the model description's directory */ public void setSrcdir(final File srcdir) { this.srcdir = srcdir; } /** * set the generated source's directory. * * @param destdirsimple * the generated source's directory */ public void setDestdirsimple(final File destdirsimple) { this.destdirsimple = destdirsimple; } /** * set the generated source's directory. * * @param destdirjoint * the generated source's directory */ public void setDestdirjoint(final File destdirjoint) { this.destdirjoint = destdirjoint; } /** * set the the directory with the code generation templates. * * @param styledir * the directory with the code generation templates */ public void setStyledir(final File styledir) { this.styledir = styledir; this.styleBean = new File(styledir, "genBean.xsl"); this.styleEnum = new File(styledir, "genEnum.xsl"); this.styleQuantity = new File(styledir, "genQuantity.xsl"); } /** * @param codeGenMode * the codeGenMode to set */ public void setCodeGenMode(CodeGenMode codeGenMode) { this.codeGenMode = codeGenMode; } /** * @param codeGenImpl * the codeGenImpl to set */ public void setCodeGenImpl(CodeGenImpl codeGenImpl) { this.codeGenImpl = codeGenImpl; } /** * @param indent * the indent to set */ public void setIndent(String indent) { this.indent = indent; } /** * the extended XSLT task. */ private XXslt xsltTask = null; /** * the package parameter. */ private XSLTProcess.Param xsltParameterPackage = null; /** * the classname parameter. */ private XSLTProcess.Param xsltParameterClassname = null; /** * the codegen parameter. */ private XSLTProcess.Param xsltParameterCodegen = null; /** * the implementation parameter. */ private XSLTProcess.Param xsltParameterImplementation = null; /** * the indent parameter. */ private XSLTProcess.Param xsltParameterIndent = null; /** * the execute method. */ public void execute() { // plausi checks for attributes if (this.srcdir == null) { throw new BuildException("No Source directory. Please define value for attribute \"srcdir\"."); } if (!this.srcdir.exists()) { throw new BuildException("Source directory \"" + this.srcdir + " not found"); } if (!this.srcdir.isDirectory()) { throw new BuildException("Invalid source directory. File \"" + this.srcdir + " is not a directory"); } if (this.destdirsimple == null) { throw new BuildException("No Destination directory. Please define value for attribute \"destdir\"."); } if (!this.destdirsimple.exists()) { throw new BuildException("Destination directory \"" + this.destdirsimple + " not found"); } if (!this.destdirsimple.isDirectory()) { throw new BuildException("Invalid destination directory. File \"" + this.destdirsimple + " is not a directory"); } if (!this.destdirsimple.exists()) { throw new BuildException("Destination directory \"" + this.destdirsimple + " not found"); } if (this.destdirjoint == null) { throw new BuildException("No Destination directory. Please define value for attribute \"destdirjoint\"."); } if (!this.destdirjoint.exists()) { throw new BuildException("Destination directory \"" + this.destdirjoint + " not found"); } if (!this.destdirjoint.isDirectory()) { throw new BuildException("Invalid destination directory. File \"" + this.destdirjoint + " is not a directory"); } if (!this.destdirjoint.exists()) { throw new BuildException("Destination directory \"" + this.destdirjoint + " not found"); } this.processDir(this.srcdir, this.destdirsimple, this.destdirjoint, ""); } /** * the lenght of extension ".xml". */ private static final int LEN_EXTENSION = 4; /** * iterates recursively over a complete directory tree. * * @param sdir * source directory * @param ddir * target directory * @param pkgname * the package name * @throws BuildException */ public void processDir(final File sdir, final File ddirsimple, final File ddirjoint, final String pkgname) { this.log("process: source directory: " + sdir.getAbsolutePath(), Project.MSG_VERBOSE); this.log("process: simple destination directory: " + ddirsimple.getAbsolutePath(), Project.MSG_VERBOSE); this.log("process: joint destination directory: " + ddirjoint.getAbsolutePath(), Project.MSG_VERBOSE); this.log("style directory: " + this.styledir.getAbsolutePath(), Project.MSG_VERBOSE); File[] sfiles = sdir.listFiles(); String sfilename; File stylesheet = null; String subPkgname; for (int i = 0; i < sfiles.length; i++) { sfilename = sfiles[i].getName(); if (sfiles[i].isDirectory()) { if (pkgname.equals("")) { subPkgname = sfilename; } else { subPkgname = pkgname + "." + sfilename; } this.processDir(sfiles[i], new File(ddirsimple, sfilename), new File(ddirjoint, sfilename), subPkgname); } else { if (!sfilename.endsWith(".xml")) { this.log("skipping non XML file \"" + sfilename + "\"", Project.MSG_VERBOSE); continue; } final CodeGenParameters cgen = this.extractCodeGenParameters(sfiles[i]); switch (cgen.getTypeOfType()) { case undefined: this.log("no description of bean type in file \"" + sfilename + "\"", Project.MSG_VERBOSE); continue; case enumtype: stylesheet = this.styleEnum; break; case quantitytype: stylesheet = this.styleQuantity; break; case beantype: stylesheet = this.styleBean; break; } File ddir = null; File tgtfile = null; final String classname = sfilename.substring(0, sfilename.length() - LEN_EXTENSION); final String pkgdirname = pkgname.replace('.', '/'); switch (cgen.getCodeGenMode()) { case simple: ddir = new File(this.destdirsimple, pkgdirname); tgtfile = new File(ddir, classname + ".java"); this.log("codegen mode: simple", Project.MSG_VERBOSE); break; case split: ddir = new File(this.destdirsimple, pkgdirname); tgtfile = new File(ddir, "RapidBeanBase" + classname + ".java"); this.log("codegen mode: split", Project.MSG_VERBOSE); break; case joint: ddir = new File(ddirjoint, pkgdirname); tgtfile = new File(ddirjoint, classname + ".java"); this.log("codegen mode: joint", Project.MSG_VERBOSE); break; case none: this.log("codegen mode: none", Project.MSG_VERBOSE); continue; default: this.log("Illegal codegen mode", Project.MSG_VERBOSE); continue; } if (!this.force && tgtfile.exists() && sfiles[i].lastModified() <= tgtfile.lastModified() && stylesheet.lastModified() <= tgtfile.lastModified()) { this.log("up to date file \"" + sfilename + "\"", Project.MSG_VERBOSE); continue; } this.transform(sfiles[i], tgtfile, stylesheet, pkgname, classname, cgen); } } } /** * Read the XML model description file and determine in advance: * * - "type" of Rapid type: bean type, enum type, quantity type determines * the template to use for code generation * * @param xmlFile * the file to check */ private CodeGenParameters extractCodeGenParameters(final File xmlFile) { try { CodeGenParameters cgen = new CodeGenParameters(); // read XML file as DOM DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance(); // dbf.setNamespaceAware(true); // boolean validate = false; // if (this.xmlValidating) // validate = true; // dbf.setValidating(validate); DocumentBuilder db = dbf.newDocumentBuilder(); Document doc = db.parse(new FileInputStream(xmlFile)); if (doc.getElementsByTagName("enumtype").item(0) != null) { cgen.setTypeOfType(TypeOfType.enumtype); } else if (doc.getElementsByTagName("quantitytype").item(0) != null) { cgen.setTypeOfType(TypeOfType.quantitytype); } else if (doc.getElementsByTagName("beantype").item(0) != null) { cgen.setTypeOfType(TypeOfType.beantype); } else { throw new BuildException("Could not determine type of Rapid type { 'bean' | 'enum' | 'quantity' }"); } if (this.codeGenMode != null) { cgen.setCodeGenMode(this.codeGenMode); } else { final Node cgenNode = doc.getElementsByTagName("codegen").item(0); if (cgenNode == null) { cgen.setCodeGenMode(DEFAULT_CODEGEN_MODE); } else { final Node modeNode = cgenNode.getAttributes().getNamedItem("mode"); if (modeNode == null) { cgen.setCodeGenMode(DEFAULT_CODEGEN_MODE); } else { final String mode = modeNode.getNodeValue(); if (mode.equalsIgnoreCase("none")) { cgen.setCodeGenMode(CodeGenMode.none); } else if (mode.equalsIgnoreCase("simple")) { cgen.setCodeGenMode(CodeGenMode.simple); } else if (mode.equalsIgnoreCase("split")) { cgen.setCodeGenMode(CodeGenMode.split); } else if (mode.equalsIgnoreCase("joint")) { cgen.setCodeGenMode(CodeGenMode.joint); } else { throw new RuntimeException("Invalid codegen mode \"" + mode + "\""); } } } } if (this.codeGenImpl != null) { cgen.setImplementation(this.codeGenImpl); } else { final Node cgenNode = doc.getElementsByTagName("codegen").item(0); if (cgenNode == null) { cgen.setImplementation(DEFAULT_CODEGEN_IMPL); } else { final Node implNode = cgenNode.getAttributes().getNamedItem("implementation"); if (implNode == null) { cgen.setImplementation(DEFAULT_CODEGEN_IMPL); } else { final String impl = implNode.getNodeValue(); if (impl.equalsIgnoreCase("strict")) { cgen.setImplementation(CodeGenImpl.strict); } else if (impl.equalsIgnoreCase("simple")) { cgen.setImplementation(CodeGenImpl.simple); } else { throw new RuntimeException("Invalid codegen implementation \"" + impl + "\""); } } } } if (this.indent != null) { cgen.setIndent(this.indent); } return cgen; } catch (IOException e) { throw new BuildException("IOException: while trying to parse XML file \"" + xmlFile.getAbsolutePath() + "\"", e); } catch (ParserConfigurationException e) { throw new BuildException("ParserConfigurationException: while trying to parse XML file \"" + xmlFile.getAbsolutePath() + "\"", e); } catch (SAXException e) { throw new BuildException("SAXException: while parsing XML file \"" + xmlFile.getAbsolutePath() + "\"", e); } } /** * transform. * * @param fsrc * the model file * @param ftgt * the target source file * @param fstyle * the code gen template * @param pkgname * the package name * @param classname * the class name */ private void transform(final File fsrc, final File ftgt, final File fstyle, final String pkgname, final String classname, final CodeGenParameters cgen) { // since Ant 1.7 we can't reuse the XSLT Task in the way we did before. // Maybe later on we could find out what we have to reset in order to // reuse // the task again this.xsltTask = new XXslt(); this.xsltTask.setProject(this.getProject()); this.xsltTask.setTaskName("xxslt"); this.xsltTask.setIn(fsrc); this.xsltTask.setOut(ftgt); this.xsltTask.setStyle(fstyle); this.xsltTask.setForce(this.force); if (cgen.getCodeGenMode() == CodeGenMode.joint) { this.xsltTask.setMerge(true); } this.xsltParameterPackage = this.xsltTask.createParam(); this.xsltParameterPackage.setName("package"); this.xsltParameterPackage.setExpression(pkgname); this.xsltParameterClassname = this.xsltTask.createParam(); this.xsltParameterClassname.setName("classname"); this.xsltParameterClassname.setExpression(classname); this.xsltParameterCodegen = this.xsltTask.createParam(); this.xsltParameterCodegen.setName("codegen"); this.xsltParameterCodegen.setExpression(cgen.getCodeGenMode().name()); this.xsltParameterImplementation = this.xsltTask.createParam(); this.xsltParameterImplementation.setName("implementation"); this.xsltParameterImplementation.setExpression(cgen.getImplementation().name()); this.xsltParameterIndent = this.xsltTask.createParam(); this.xsltParameterIndent.setName("indent"); this.xsltParameterIndent.setExpression(cgen.getIndent()); this.log("IN File \"" + fsrc.getAbsolutePath() + "\"", Project.MSG_VERBOSE); this.log("OUT File \"" + ftgt.getAbsolutePath() + "\"", Project.MSG_VERBOSE); this.log("STYLE File \"" + fstyle.getAbsolutePath() + "\"", Project.MSG_VERBOSE); this.log("performing RapidBeans code generation of class \"" + ftgt, Project.MSG_VERBOSE); this.xsltTask.execute(); } private class CodeGenParameters { /** * the type of RapidBeans type. */ private TypeOfType typeOfType = TypeOfType.undefined; /** * code generation mode. */ private CodeGenMode codeGenMode = null; /** * code generation implementation. */ private CodeGenImpl implementation = null; /** * code generation indentation string. */ private String indent = null; /** * @return the typeOfType */ protected TypeOfType getTypeOfType() { return typeOfType; } /** * @param typeOfType * the typeOfType to set */ protected void setTypeOfType(TypeOfType typeOfType) { this.typeOfType = typeOfType; } /** * @return the mode */ protected CodeGenMode getCodeGenMode() { return codeGenMode; } /** * @param mode * the mode to set */ protected void setCodeGenMode(CodeGenMode codeGenMode) { this.codeGenMode = codeGenMode; } protected CodeGenImpl getImplementation() { return implementation; } /** * @param implementation * the implementation to set */ protected void setImplementation(CodeGenImpl implementation) { this.implementation = implementation; } protected String getIndent() { return indent; } /** * @param indent * the indent to set */ protected void setIndent(String indent) { this.indent = indent; } } }