/*
* Rapid Beans Framework, SDK, Maven Plugin: ModelGenerator.java
*
* Copyright (C) 2013 Martin Bluemel
*
* Creation Date: 01/21/2013
*
* 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.maven.generator;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.URL;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.Result;
import javax.xml.transform.Source;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerException;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.stream.StreamResult;
import javax.xml.transform.stream.StreamSource;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.maven.plugin.logging.Log;
import org.rapidbeans.maven.exceptions.BuildException;
import org.rapidbeans.maven.generator.utils.CodeGenMode;
import org.rapidbeans.maven.generator.utils.TypeOfType;
import org.w3c.dom.Document;
import org.w3c.dom.Node;
import org.xml.sax.SAXException;
import com.google.common.base.Strings;
/**
* the genclasses task.
*
* @author Mischur.Alexander
*/
public class ModelGenerator extends AbstractGenerator {
private static final String DEFAULT_STYLE_BEAN = "genBean.xsl";
private static final String DEFAULT_STYLE_QUANTITY = "genQuantity.xsl";
private static final String DEFAULT_STYLE_ENUM = "genEnum.xsl";
private static final String DEFAULT_STYLE_DIR = "styles/";
private final String defaultPkgName;
/**
* 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;
public ModelGenerator(File srcdir, File destdirsimple, File destdirjoint, String defaultPkgName, Log log) {
this(srcdir, destdirsimple, destdirjoint, false, defaultPkgName, log);
}
public ModelGenerator(File srcdir, File destdirsimple, File destdirjoint, boolean force, String defaultPkgName,
Log log) {
super(srcdir, destdirsimple, destdirjoint, force, log);
this.defaultPkgName = defaultPkgName;
}
public File getStyledir() {
if (styledir == null) {
styledir = new File(DEFAULT_STYLE_DIR);
}
return styledir;
}
public void setStyledir(File styledir) {
this.styledir = styledir;
}
public File getStyleEnum() {
if (styleEnum == null) {
styleEnum = new File(getStyledir(), DEFAULT_STYLE_ENUM);
}
return styleEnum;
}
public void setStyleEnum(File styleEnum) {
this.styleEnum = styleEnum;
}
public File getStyleQuantity() {
if (styleQuantity == null) {
styleQuantity = new File(getStyledir(), DEFAULT_STYLE_QUANTITY);
}
return styleQuantity;
}
public void setStyleQuantity(File styleQuantity) {
this.styleQuantity = styleQuantity;
}
public File getStyleBean() {
if (styleBean == null) {
styleBean = new File(getStyledir(), DEFAULT_STYLE_BEAN);
}
return styleBean;
}
public void setStyleBean(File styleBean) {
this.styleBean = styleBean;
}
public String getDefaultPkgName() {
return defaultPkgName;
}
public boolean hasDefaultPkgName() {
return !Strings.isNullOrEmpty(this.defaultPkgName);
}
@Override
public void execute() throws BuildException {
this.processDir(getSrcdir(), getDestdirsimple(), getDestdirjoint(), "");
}
/**
* iterates recursively over a complete directory tree.
*
* @param sdir
* source directory
* @param ddirsimple
* directory where generated files are stored
* @param ddirjoint
* directory where merged files are stored
* @param pkgname
* the package name
* @throws BuildException
*/
private void processDir(File sdir, File ddirsimple, File ddirjoint, String pkgname) throws BuildException {
if (log.isDebugEnabled()) {
log.debug("process: source directory: " + sdir.getAbsolutePath());
log.debug("process: simple destination directory: " + ddirsimple.getAbsolutePath());
log.debug("process: joint destination directory: " + ddirjoint.getAbsolutePath());
log.debug("style directory: " + getStyledir().getAbsolutePath());
}
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(AbstractGenerator.FILE_ENDING_XML)) {
log.debug("skipping non XML file \"" + sfilename + "\"");
continue;
}
final CodeGenInfo cgen = this.check(sfiles[i]);
switch (cgen.getTypeOfType()) {
case undefined:
log.debug("no description of bean type in file \"" + sfilename + "\". Skipping it.");
continue;
case enumtype:
stylesheet = getStyleEnum();
break;
case quantitytype:
stylesheet = getStyleQuantity();
break;
case beantype:
stylesheet = getStyleBean();
break;
}
File ddir = null;
File tgtfile = null;
final String classname = sfilename.substring(0, sfilename.length()
- AbstractGenerator.FILE_ENDING_XML_LENGTH);
final String pkgdirname = pkgname.replace('.', '/');
switch (cgen.getMode()) {
case simple:
ddir = new File(ddirsimple, pkgdirname);
tgtfile = new File(ddir, classname + ".java");
log.debug("codegen mode: simple");
break;
case split:
ddir = new File(ddirsimple, pkgdirname);
tgtfile = new File(ddir, "RapidBeanBase" + classname + ".java");
log.debug("codegen mode: split");
break;
case joint:
ddir = new File(ddirjoint, pkgdirname);
tgtfile = new File(ddirjoint, classname + ".java");
log.debug("codegen mode: joint");
break;
case none:
log.debug("codegen mode: none");
continue;
default:
log.debug("Illegal codegen mode");
continue;
}
if (!isForce() && tgtfile.exists() && sfiles[i].lastModified() <= tgtfile.lastModified()
&& stylesheet.lastModified() <= tgtfile.lastModified()) {
log.debug("up to date file \"" + sfilename + "\"");
continue;
}
InputStream srcIs = null;
OutputStream tgtOs = null;
InputStream styleIs = null;
try {
srcIs = FileUtils.openInputStream(sfiles[i]);
tgtOs = FileUtils.openOutputStream(tgtfile);
boolean isDefault = false;
if (stylesheet == null || !stylesheet.exists() || !stylesheet.canRead() || !stylesheet.isFile()) {
log.warn("Given Stylesheets could not be found! Reading default ones from classpath");
switch (cgen.getTypeOfType()) {
case enumtype:
styleIs = readFileFromClasspath(DEFAULT_STYLE_DIR + DEFAULT_STYLE_ENUM).openStream();
break;
case quantitytype:
styleIs = readFileFromClasspath(DEFAULT_STYLE_DIR + DEFAULT_STYLE_QUANTITY).openStream();
break;
case beantype:
styleIs = readFileFromClasspath(DEFAULT_STYLE_DIR + DEFAULT_STYLE_BEAN).openStream();
break;
default:
throw new BuildException("unknown stylesheet type!");
}
isDefault = true;
} else {
styleIs = FileUtils.openInputStream(stylesheet);
}
this.transform(srcIs, tgtOs, styleIs, isDefault, pkgname, classname, cgen);
} catch (TransformerException e) {
throw new BuildException(e);
} catch (IOException e) {
throw new BuildException(e);
} catch (NullPointerException e) {
throw new BuildException(e);
} finally {
IOUtils.closeQuietly(srcIs);
IOUtils.closeQuietly(tgtOs);
IOUtils.closeQuietly(styleIs);
}
}
}
}
/**
* check the XML model description file.
*
* @param xmlFile
* the file to check
*/
private CodeGenInfo check(final File xmlFile) {
try {
CodeGenInfo cgen = new CodeGenInfo();
// 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 {
return cgen;
}
final Node cgenNode = doc.getElementsByTagName("codegen").item(0);
if (cgenNode != null) {
final String mode = cgenNode.getAttributes().getNamedItem("mode").getNodeValue();
if (mode.equalsIgnoreCase("none")) {
cgen.setMode(CodeGenMode.none);
} else if (mode.equalsIgnoreCase("simple")) {
cgen.setMode(CodeGenMode.simple);
} else if (mode.equalsIgnoreCase("split")) {
cgen.setMode(CodeGenMode.split);
} else if (mode.equalsIgnoreCase("joint")) {
cgen.setMode(CodeGenMode.joint);
}
}
return cgen;
} catch (ParserConfigurationException e) {
throw new BuildException(e);
} catch (FileNotFoundException e) {
throw new BuildException(e);
} catch (SAXException e) {
throw new BuildException(e);
} catch (IOException e) {
throw new BuildException(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
* @throws TransformerException
* @throws IOException
*/
private void transform(InputStream srcIs, OutputStream tgtOs, InputStream styleIs, boolean isDefault,
String pkgname,
String classname, CodeGenInfo cgen)
throws TransformerException, IOException {
Source xmlSource = new StreamSource(srcIs);
Source xsltSource = new StreamSource(styleIs);
if (isDefault) {
xsltSource.setSystemId(readFileFromClasspath(DEFAULT_STYLE_DIR).toString());
}
Result result = new StreamResult(tgtOs);
TransformerFactory transFact = TransformerFactory.newInstance();
Transformer trans = transFact.newTransformer(xsltSource);
trans.setParameter("package", pkgname);
trans.setParameter("classname", classname);
trans.setParameter("codegen", cgen.getMode().name());
trans.transform(xmlSource, result);
}
private URL readFileFromClasspath(String filePath) throws IOException {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
URL mapUrl = cl.getResource(filePath);
if (null == mapUrl) {
// fall back to own class loader if TCCL does not find the resource
mapUrl = ModelGenerator.class.getResource(filePath);
}
return mapUrl;
}
}