
de.tsl2.nano.codegen.ACodeGenerator Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of tsl2.nano.generator Show documentation
Show all versions of tsl2.nano.generator Show documentation
velocity template generator through classes or dom
package de.tsl2.nano.codegen;
import java.io.BufferedWriter;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.sql.Timestamp;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.velocity.Template;
import org.apache.velocity.VelocityContext;
import org.apache.velocity.app.VelocityEngine;
import org.apache.velocity.exception.MethodInvocationException;
import org.apache.velocity.exception.ParseErrorException;
import org.apache.velocity.exception.ResourceNotFoundException;
import de.tsl2.nano.core.Arg;
import de.tsl2.nano.core.Argumentator;
import de.tsl2.nano.core.ManagedException;
import de.tsl2.nano.core.cls.BeanClass;
import de.tsl2.nano.core.cls.ClassFinder;
import de.tsl2.nano.core.log.LogFactory;
import de.tsl2.nano.core.util.FileUtil;
import de.tsl2.nano.core.util.StringUtil;
import de.tsl2.nano.core.util.Util;
/**
*
* Prepares and Defines a Velocity Template Generator
*
* extensions may override the methods:
*
* @see #getDefaultDestinationFile(String)
*
* The default implementation of this basic generator expects a java bean
* (class package) and creates a java file.
*
* Howto start (example): mainClass:
* de.tsl2.nano.codegen.ACodeGenerator arguments:
* bin/codegen/beanclass.vm
* @author ts 07.12.2008
* @version $Revision: 1.0 $
*/
public abstract class ACodeGenerator {
//direct declared arguments
public static final String KEY_PROPERTIES = "properties";
public static final String KEY_FILTER = "filter";
public static final String KEY_TEMPLATE = "template";
public static final String KEY_MODEL = "model";
public static final String KEY_MODELFILE = "modelFile";
public static final String KEY_ALGORITHM = "algorithm";
//indirect system properties (using #KEY_PREFIX)
public static final String KEY_PREFIX = "bean.generation.";
public static final String KEY_PACKAGENAME = KEY_PREFIX + "packagename";
public static final String KEY_OUTPUTPATH = KEY_PREFIX + "outputpath";
public static final String KEY_NAMEPREFIX = KEY_PREFIX + "nameprefix";
public static final String KEY_NAMEPOSTFIX = KEY_PREFIX + "namepostfix";
public static final String KEY_UNPACKAGED = KEY_PREFIX + "unpackaged";
public static final String KEY_SINGLEFILE = KEY_PREFIX + "singlefile";
public static final String KEY_INSTANCEABLE = KEY_PREFIX + "filter.instanceable";
public static final String KEY_ANNOTATED = KEY_PREFIX + "filter.annotated";
public static final String KEY_INSTANCEOF = KEY_PREFIX + "filter.instanceof";
public static final String KEY_KEYWORDLIST = KEY_PREFIX + "keyword.list";
public static final String KEY_KEYWORDREPL = KEY_PREFIX + "keyword.replacement";
private VelocityEngine engine;
String codeTemplate;
private GeneratorUtility utilityInstance;
Properties properties = null;
long count = 0;
Map keywordMap;
protected static final Log LOG = LogFactory.getLog(ACodeGenerator.class);
public static final String DEFAULT_DEST_PREFIX = "src/gen";
protected ACodeGenerator() {
initEngine();
}
/**
* initializes the velocity engine.
*/
protected void initEngine() {
// Init Velocity
// Velocity.init();
engine = new VelocityEngine();
engine.addProperty("resource.loader", "file, class, jar");
engine.addProperty("file.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.FileResourceLoader");
engine.addProperty("class.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
engine.addProperty("jar.resource.loader.class",
"org.apache.velocity.runtime.resource.loader.JarResourceLoader");
try {
engine.init();
LOG.debug("ACodeGenerator has initialized velocity template engine");
} catch (final Exception e) {
ManagedException.forward(e);
}
// Velocity.setProperty( Velocity.RUNTIME_LOG_LOGSYSTEM, new Log());
}
/**
* @return properties for the velocity context
*/
public Properties getProperties() {
if (properties == null) {
properties = new Properties();
}
return properties;
}
public static void start(String[] mainArgs, Properties props) {
start(mainArgs, props, 1);
}
public static void start(String[] mainArgs, Properties props, int errorExitCode) {
Argumentator am = new Argumentator(ACodeGenerator.class.getSimpleName(), getManual(), errorExitCode, mainArgs);
am.start(System.out, a -> {
ACodeGenerator gen = (ACodeGenerator) BeanClass.createInstance(a.getProperty(KEY_ALGORITHM));
Map keywordMap = createKeywordMap();
if (keywordMap != null) {
gen.keywordMap = keywordMap;
}
if (props != null)
gen.getProperties().putAll(props);
gen.start(a);
return gen.getClass().getSimpleName() + " finished successfull! generated sources: " + gen.count;
});
}
private static Map createKeywordMap() {
String keywordList = System.getProperty(KEY_KEYWORDLIST);
if (keywordList == null)
return null;
List keywords = Arrays.asList(keywordList.split("[,;|\\w]"));
String keywordRepl = System.getProperty(KEY_KEYWORDREPL);
List repl = Arrays.asList(keywordRepl.split("[,;|\\w]"));
boolean byExtension = repl.size() == 1 && keywords.size() > 1;
if (!byExtension && keywords.size() != repl.size())
throw new IllegalArgumentException(KEY_KEYWORDLIST + " size must be equal to " + KEY_KEYWORDREPL
+ " size (" + keywords.size() + " != " + repl.size() + ") - or " + KEY_KEYWORDREPL + " may be one");
Map keywordMap = new HashMap<>();
for (int i = 0; i < keywords.size(); i++) {
keywordMap.put(keywords.get(i), repl.get(byExtension ? 0 : i));
}
return keywordMap;
}
public abstract void start(Properties args);
public void initParameter(Properties args) {
codeTemplate = args.getProperty(KEY_TEMPLATE);
if (args.getProperty(KEY_PROPERTIES) != null)
getProperties().putAll(FileUtil.loadProperties(args.getProperty(KEY_PROPERTIES),
Thread.currentThread().getContextClassLoader()));
}
/**
* @see #generate(String, String, String, Properties, ClassLoader)
*/
public void generate(Object model, String modelFile, String templateFile, Properties properties) {
generate(model, modelFile, templateFile, getDefaultDestinationFile(modelFile),
properties == null ? new Properties() : properties);
}
/**
* generates a code file through the information of the 'modelFile' and the
* template 'templateFile'. The generate code file will be stored at 'destFile'.
*
* @param modelFile the source file
* @param templateFile a velocity template (full path, not classpath!)
* @param destFile the destination file
*/
public void generate(Object model, String modelFile, String templateFile, String destFile, Properties properties) {
// if (templateFile == null || !new File(templateFile).canRead()) {
// throw new IllegalStateException("template file " + templateFile + " not
// readable.");
// }
final VelocityContext context = new VelocityContext();
LOG.info("generating " + templateFile + " + " + modelFile + " ==> " + destFile);
fillVelocityContext(model, modelFile, templateFile, destFile, properties, getUtil(), context);
final Template template = engine.getTemplate(templateFile);
final File file = FileUtil.userDirFile(destFile);
if (file.getParentFile() != null)
file.getParentFile().mkdirs();
try {
final BufferedWriter writer = new BufferedWriter(new FileWriter(file));
template.merge(context, writer);
writer.flush();
writer.close();
count++;
} catch (ResourceNotFoundException | ParseErrorException | MethodInvocationException | IOException e) {
ManagedException.forward(e);
}
}
protected void fillVelocityContext(Object model, String modelFile, String templateFile, String destFile,
Properties properties, final GeneratorUtility util, final VelocityContext context) {
fillVelocityProperties(context, properties);
context.put(KEY_MODEL, model);
context.put(KEY_MODELFILE, modelFile);
context.put(KEY_TEMPLATE, templateFile);
context.put("util", util);
context.put("time", new Timestamp(System.currentTimeMillis()));
context.put("copyright", "Copyright (c) 2002-2020 Thomas Schneider");
context.put("path", getDestinationPackageName(modelFile, destFile));
context.put("postfix", getDestinationPostfix(modelFile));
context.put("eventhandler.methodexception.class", DontEscalateExceptionHandler.class);
util.setContext(context);
}
private void fillVelocityProperties(VelocityContext context, Properties properties) {
for (final Object p : properties.keySet()) {
final Object v = replaceKeyword(properties.get(p));
if (context.containsKey(p)) {
LOG.error("name clash in velocity context on key '" + p + "': existing generator value: "
+ context.get(p.toString()) + ", user property: " + v + " will be ignored!");
continue;
}
context.put((String) p, v);
LOG.debug("adding velocity context property: " + p + "=" + v);
}
}
private final Object replaceKeyword(Object contextValue) {
if (keywordMap == null || !keywordMap.containsKey(contextValue))
return contextValue;
return keywordMap.get(contextValue);
}
protected GeneratorUtility getUtil() {
if (utilityInstance == null)
utilityInstance = createUtilityInstance();
return utilityInstance;
}
/**
* override this method to use your special utility
*
* @return utility instance
*/
protected GeneratorUtility createUtilityInstance() {
return new GeneratorUtility();
}
/**
* tries to extract the destination class name. precondition is, that
* package-path starts equal on model and destination!
*
* @param sourceFile source class name
* @param destinationFileName dest file name
* @return dest class name
*/
protected String getDestinationClassName(String sourceFile, String destinationFileName) {
String dest = destinationFileName.replace('/', '.');
dest = dest.substring(0, dest.lastIndexOf('.'));
final String src = sourceFile.contains(".") ? sourceFile.substring(0, sourceFile.lastIndexOf('.')) : sourceFile;
int isrc = dest.indexOf(src);
return isrc > -1 ? dest.substring(isrc) : dest;
}
/**
* override this method to provide a default destination file
*
* @see #getModel(String, ClassLoader)
*
* @param modelFile source file
* @return default classloader
*/
protected String getDefaultDestinationFile(String modelFile) {
boolean singleFile = Boolean.getBoolean(KEY_SINGLEFILE);
if (singleFile)
modelFile = ""; // only the destination postfix is the name!
else
modelFile = getDestinationPrefix(modelFile);
String path = Util.get(KEY_OUTPUTPATH, DEFAULT_DEST_PREFIX);
return (path.endsWith("/") ? path : path + "/") + modelFile + getDestinationPostfix(modelFile);
}
private String getDestinationPrefix(String modelFile) {
boolean unpackaged = Boolean.getBoolean(KEY_UNPACKAGED);
String defaultPrefix = unpackaged ? StringUtil.substring(modelFile, ".", null, true) : modelFile.replace('.', '/');
String prefix = Util.get(KEY_NAMEPREFIX, defaultPrefix);
String regexExtraction = StringUtil.extract(modelFile, prefix);
return Util.isEmpty(regexExtraction) ? prefix : regexExtraction;
}
protected String getDestinationPostfix(String modelFile) {
String postfix = Util.get(KEY_NAMEPOSTFIX, StringUtil.toFirstUpper(extractName(codeTemplate)) + ".java");
String regexExtraction = StringUtil.extract(modelFile, postfix);
return Util.isEmpty(regexExtraction) ? postfix : regexExtraction;
}
/**
* extracts the package name from the result of
* {@link #getDestinationClassName(String, String)}.
*
* @param sourceFile source class name
* @param destinationFileName dest file name
* @return dest package name
* @see #getDestinationClassName(String, String)
*/
protected String getDestinationPackageName(String sourceFile, String destinationFileName) {
final String p = getDestinationClassName(sourceFile, destinationFileName);
return p.indexOf('.') != -1 ? p.substring(0, p.lastIndexOf('.')) : p;
}
public static String extractName(String filePath) {
return StringUtil.substring(filePath, "/", ".", true);
}
public static void main(String args[]) throws Exception {
start(args, null);
}
private static Map> getManual() {
Collection> gens = ClassFinder.self().findClass(ACodeGenerator.class);
List templates = FileUtil.getFileset("./", "**/*.vm");
Map> man = new LinkedHashMap<>();
man.put(KEY_ALGORITHM, new Arg(KEY_ALGORITHM, true, null, new ArrayList<>(gens), null, "(!) generator implementation class", null));
man.put(KEY_MODEL, new Arg(KEY_MODEL, "(!) model-file to read source informations from"));
man.put(KEY_TEMPLATE, new Arg(KEY_TEMPLATE, true, null, templates, null, "(!) velocity template-file to generate new code from and input source informations", null));
man.put(KEY_FILTER, new Arg(KEY_FILTER, "( ) additional source filter"));
man.put(KEY_PROPERTIES, new Arg(KEY_PROPERTIES, "( ) property-file to read additional informations"));
man.put("system variables", new Arg("system variables", "\n"
+ " - " + KEY_PACKAGENAME + " : only class in that package\n"
+ " - " + KEY_OUTPUTPATH + " : output base path (default: src/gen)\n"
+ " - " + KEY_NAMEPREFIX + " : class+package name prefix (default: package + code-template) may be a regex of model\n"
+ " - " + KEY_NAMEPOSTFIX + " : class name postfix (default: {code-template}.java) may be a regex of model\n"
+ " - " + KEY_UNPACKAGED + " : no package structure from origin will be inherited (default: false)\n"
+ " - " + KEY_SINGLEFILE + " : generate only the first occurrency (default: false)\n"
+ " - " + KEY_INSTANCEABLE + ": filter all not instanceable classes\n"
+ " - " + KEY_ANNOTATED + " : annotation to search for. all not annotated classes will be filtered\n"
+ " - " + KEY_INSTANCEOF + " : base/super class. all not extending classes will be filtered\n"
+ " - " + KEY_KEYWORDLIST + " : list of comma-separated keywords to be replaced through keyword.replacement\n"
+ " - " + KEY_KEYWORDREPL + " : list of csv keyword replacements. may be a single string to be added to all keywords\n"
+ " - " + KEY_PREFIX + "catchedclass : class name not throwing exceptions on method 'cachedmethod'\n"
+ " - " + KEY_PREFIX + "catchedmethod: method name not throwing exceptions on class 'cachedclass'\n"
));
return man;
}
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy