| package org.apache.velocity.texen.ant; |
| |
| /* |
| * Copyright 2001-2004 The Apache Software Foundation. |
| * |
| * Licensed under the Apache License, Version 2.0 (the "License") |
| * you may not use this file except in compliance with the License. |
| * You may obtain a copy of the License at |
| * |
| * http://www.apache.org/licenses/LICENSE-2.0 |
| * |
| * Unless required by applicable law or agreed to in writing, software |
| * distributed under the License is distributed on an "AS IS" BASIS, |
| * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| * See the License for the specific language governing permissions and |
| * limitations under the License. |
| */ |
| |
| import java.io.File; |
| import java.io.FileInputStream; |
| import java.io.IOException; |
| import java.io.InputStream; |
| import java.io.Writer; |
| import java.util.Date; |
| import java.util.Iterator; |
| import java.util.StringTokenizer; |
| |
| import org.apache.commons.collections.ExtendedProperties; |
| import org.apache.tools.ant.BuildException; |
| import org.apache.tools.ant.Project; |
| import org.apache.tools.ant.Task; |
| import org.apache.velocity.VelocityContext; |
| import org.apache.velocity.app.VelocityEngine; |
| import org.apache.velocity.context.Context; |
| import org.apache.velocity.exception.MethodInvocationException; |
| import org.apache.velocity.exception.ParseErrorException; |
| import org.apache.velocity.exception.ResourceNotFoundException; |
| import org.apache.velocity.runtime.RuntimeConstants; |
| import org.apache.velocity.texen.Generator; |
| import org.apache.velocity.util.StringUtils; |
| |
| /** |
| * An ant task for generating output by using Velocity |
| * |
| * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a> |
| * @author <a href="robertdonkin@mac.com">Robert Burrell Donkin</a> |
| * @version $Id$ |
| */ |
| public class TexenTask |
| extends Task |
| { |
| /** |
| * This message fragment (telling users to consult the log or |
| * invoke ant with the -debug flag) is appended to rethrown |
| * exception messages. |
| */ |
| private final static String ERR_MSG_FRAGMENT = |
| ". For more information consult the velocity log, or invoke ant " + |
| "with the -debug flag."; |
| |
| /** |
| * This is the control template that governs the output. |
| * It may or may not invoke the services of worker |
| * templates. |
| */ |
| protected String controlTemplate; |
| |
| /** |
| * This is where Velocity will look for templates |
| * using the file template loader. |
| */ |
| protected String templatePath; |
| |
| /** |
| * This is where texen will place all the output |
| * that is a product of the generation process. |
| */ |
| protected String outputDirectory; |
| |
| /** |
| * This is the file where the generated text |
| * will be placed. |
| */ |
| protected String outputFile; |
| |
| /** |
| * This is the encoding for the output file(s). |
| */ |
| protected String outputEncoding; |
| |
| /** |
| * This is the encoding for the input file(s) |
| * (templates). |
| */ |
| protected String inputEncoding; |
| |
| /** |
| * <p> |
| * These are properties that are fed into the |
| * initial context from a properties file. This |
| * is simply a convenient way to set some values |
| * that you wish to make available in the context. |
| * </p> |
| * <p> |
| * These values are not critical, like the template path |
| * or output path, but allow a convenient way to |
| * set a value that may be specific to a particular |
| * generation task. |
| * </p> |
| * <p> |
| * For example, if you are generating scripts to allow |
| * user to automatically create a database, then |
| * you might want the <code>$databaseName</code> |
| * to be placed |
| * in the initial context so that it is available |
| * in a script that might look something like the |
| * following: |
| * <code><pre> |
| * #!bin/sh |
| * |
| * echo y | mysqladmin create $databaseName |
| * </pre></code> |
| * The value of <code>$databaseName</code> isn't critical to |
| * output, and you obviously don't want to change |
| * the ant task to simply take a database name. |
| * So initial context values can be set with |
| * properties file. |
| */ |
| protected ExtendedProperties contextProperties; |
| |
| /** |
| * Property which controls whether the classpath |
| * will be used when trying to locate templates. |
| */ |
| protected boolean useClasspath; |
| |
| /** |
| * The LogFile (incl. path) to log to. |
| */ |
| protected String logFile; |
| |
| /** |
| * Property which controls whether the resource |
| * loader will be told to cache. Default false |
| */ |
| |
| protected String useResourceLoaderCache = "false"; |
| /** |
| * |
| */ |
| protected String resourceLoaderModificationCheckInterval = "2"; |
| |
| /** |
| * [REQUIRED] Set the control template for the |
| * generating process. |
| * @param controlTemplate |
| */ |
| public void setControlTemplate (String controlTemplate) |
| { |
| this.controlTemplate = controlTemplate; |
| } |
| |
| /** |
| * Get the control template for the |
| * generating process. |
| * @return The current control template. |
| */ |
| public String getControlTemplate() |
| { |
| return controlTemplate; |
| } |
| |
| /** |
| * [REQUIRED] Set the path where Velocity will look |
| * for templates using the file template |
| * loader. |
| * @param templatePath |
| * @throws Exception |
| */ |
| |
| public void setTemplatePath(String templatePath) throws Exception |
| { |
| StringBuffer resolvedPath = new StringBuffer(); |
| StringTokenizer st = new StringTokenizer(templatePath, ","); |
| while ( st.hasMoreTokens() ) |
| { |
| // resolve relative path from basedir and leave |
| // absolute path untouched. |
| File fullPath = project.resolveFile(st.nextToken()); |
| resolvedPath.append(fullPath.getCanonicalPath()); |
| if ( st.hasMoreTokens() ) |
| { |
| resolvedPath.append(","); |
| } |
| } |
| this.templatePath = resolvedPath.toString(); |
| |
| System.out.println(templatePath); |
| } |
| |
| /** |
| * Get the path where Velocity will look |
| * for templates using the file template |
| * loader. |
| * @return The template path. |
| */ |
| public String getTemplatePath() |
| { |
| return templatePath; |
| } |
| |
| /** |
| * [REQUIRED] Set the output directory. It will be |
| * created if it doesn't exist. |
| * @param outputDirectory |
| */ |
| public void setOutputDirectory(File outputDirectory) |
| { |
| try |
| { |
| this.outputDirectory = outputDirectory.getCanonicalPath(); |
| } |
| catch (java.io.IOException ioe) |
| { |
| throw new BuildException(ioe); |
| } |
| } |
| |
| /** |
| * Get the output directory. |
| * @return The output directory. |
| */ |
| public String getOutputDirectory() |
| { |
| return outputDirectory; |
| } |
| |
| /** |
| * [REQUIRED] Set the output file for the |
| * generation process. |
| * @param outputFile |
| */ |
| public void setOutputFile(String outputFile) |
| { |
| this.outputFile = outputFile; |
| } |
| |
| /** |
| * Set the output encoding. |
| * @param outputEncoding |
| */ |
| public void setOutputEncoding(String outputEncoding) |
| { |
| this.outputEncoding = outputEncoding; |
| } |
| |
| /** |
| * Set the input (template) encoding. |
| * @param inputEncoding |
| */ |
| public void setInputEncoding(String inputEncoding) |
| { |
| this.inputEncoding = inputEncoding; |
| } |
| |
| /** |
| * Get the output file for the |
| * generation process. |
| * @return The output file. |
| */ |
| public String getOutputFile() |
| { |
| return outputFile; |
| } |
| |
| /** |
| * Sets the log file. |
| * @param log |
| */ |
| public void setLogFile(String log) |
| { |
| this.logFile = log; |
| } |
| |
| /** |
| * Gets the log file. |
| * @return The log file. |
| */ |
| public String getLogFile() |
| { |
| return this.logFile; |
| } |
| |
| /** |
| * Set the context properties that will be |
| * fed into the initial context be the |
| * generating process starts. |
| * @param file |
| */ |
| public void setContextProperties( String file ) |
| { |
| String[] sources = StringUtils.split(file,","); |
| contextProperties = new ExtendedProperties(); |
| |
| // Always try to get the context properties resource |
| // from a file first. Templates may be taken from a JAR |
| // file but the context properties resource may be a |
| // resource in the filesystem. If this fails than attempt |
| // to get the context properties resource from the |
| // classpath. |
| for (int i = 0; i < sources.length; i++) |
| { |
| ExtendedProperties source = new ExtendedProperties(); |
| |
| try |
| { |
| // resolve relative path from basedir and leave |
| // absolute path untouched. |
| File fullPath = project.resolveFile(sources[i]); |
| log("Using contextProperties file: " + fullPath); |
| source.load(new FileInputStream(fullPath)); |
| } |
| catch (IOException e) |
| { |
| ClassLoader classLoader = this.getClass().getClassLoader(); |
| |
| try |
| { |
| InputStream inputStream = classLoader.getResourceAsStream(sources[i]); |
| |
| if (inputStream == null) |
| { |
| throw new BuildException("Context properties file " + sources[i] + |
| " could not be found in the file system or on the classpath!"); |
| } |
| else |
| { |
| source.load(inputStream); |
| } |
| } |
| catch (IOException ioe) |
| { |
| source = null; |
| } |
| } |
| |
| if (source != null) |
| { |
| for (Iterator j = source.getKeys(); j.hasNext(); ) |
| { |
| String name = (String) j.next(); |
| String value = StringUtils.nullTrim(source.getString(name)); |
| contextProperties.setProperty(name,value); |
| } |
| } |
| } |
| } |
| |
| /** |
| * Get the context properties that will be |
| * fed into the initial context be the |
| * generating process starts. |
| * @return The current context properties. |
| */ |
| public ExtendedProperties getContextProperties() |
| { |
| return contextProperties; |
| } |
| |
| /** |
| * Set the use of the classpath in locating templates |
| * |
| * @param useClasspath true means the classpath will be used. |
| */ |
| public void setUseClasspath(boolean useClasspath) |
| { |
| this.useClasspath = useClasspath; |
| } |
| |
| /** |
| * @param useResourceLoaderCache |
| */ |
| public void setUseResourceLoaderCache(String useResourceLoaderCache) |
| { |
| this.useResourceLoaderCache = useResourceLoaderCache; |
| } |
| |
| /** |
| * @param resourceLoaderModificationCheckInterval |
| */ |
| public void setResourceLoaderModificationCheckInterval(String resourceLoaderModificationCheckInterval) |
| { |
| this.resourceLoaderModificationCheckInterval = resourceLoaderModificationCheckInterval; |
| } |
| /** |
| * Creates a VelocityContext. |
| * |
| * @return new Context |
| * @throws Exception the execute method will catch |
| * and rethrow as a <code>BuildException</code> |
| */ |
| public Context initControlContext() |
| throws Exception |
| { |
| return new VelocityContext(); |
| } |
| |
| /** |
| * Execute the input script with Velocity |
| * |
| * @throws BuildException |
| * BuildExceptions are thrown when required attributes are missing. |
| * Exceptions thrown by Velocity are rethrown as BuildExceptions. |
| */ |
| public void execute () |
| throws BuildException |
| { |
| // Make sure the template path is set. |
| if (templatePath == null && useClasspath == false) |
| { |
| throw new BuildException( |
| "The template path needs to be defined if you are not using " + |
| "the classpath for locating templates!"); |
| } |
| |
| // Make sure the control template is set. |
| if (controlTemplate == null) |
| { |
| throw new BuildException("The control template needs to be defined!"); |
| } |
| |
| // Make sure the output directory is set. |
| if (outputDirectory == null) |
| { |
| throw new BuildException("The output directory needs to be defined!"); |
| } |
| |
| // Make sure there is an output file. |
| if (outputFile == null) |
| { |
| throw new BuildException("The output file needs to be defined!"); |
| } |
| |
| VelocityEngine ve = new VelocityEngine(); |
| |
| try |
| { |
| // Setup the Velocity Runtime. |
| if (templatePath != null) |
| { |
| log("Using templatePath: " + templatePath, Project.MSG_VERBOSE); |
| ve.setProperty( |
| RuntimeConstants.FILE_RESOURCE_LOADER_PATH, templatePath); |
| } |
| |
| if (useClasspath) |
| { |
| log("Using classpath"); |
| ve.addProperty( |
| VelocityEngine.RESOURCE_LOADER, "classpath"); |
| |
| ve.setProperty( |
| "classpath." + VelocityEngine.RESOURCE_LOADER + ".class", |
| "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader"); |
| |
| ve.setProperty( |
| "classpath." + VelocityEngine.RESOURCE_LOADER + |
| ".cache", useResourceLoaderCache); |
| |
| ve.setProperty( |
| "classpath." + VelocityEngine.RESOURCE_LOADER + |
| ".modificationCheckInterval", resourceLoaderModificationCheckInterval); |
| } |
| |
| if (this.logFile != null) |
| { |
| ve.setProperty(RuntimeConstants.RUNTIME_LOG, this.logFile); |
| } |
| |
| ve.init(); |
| |
| // Create the text generator. |
| Generator generator = Generator.getInstance(); |
| generator.setVelocityEngine(ve); |
| generator.setOutputPath(outputDirectory); |
| generator.setInputEncoding(inputEncoding); |
| generator.setOutputEncoding(outputEncoding); |
| |
| if (templatePath != null) |
| { |
| generator.setTemplatePath(templatePath); |
| } |
| |
| // Make sure the output directory exists, if it doesn't |
| // then create it. |
| File file = new File(outputDirectory); |
| if (! file.exists()) |
| { |
| file.mkdirs(); |
| } |
| |
| String path = outputDirectory + File.separator + outputFile; |
| log("Generating to file " + path, Project.MSG_INFO); |
| Writer writer = generator.getWriter(path, outputEncoding); |
| |
| // The generator and the output path should |
| // be placed in the init context here and |
| // not in the generator class itself. |
| Context c = initControlContext(); |
| |
| // Everything in the generator class should be |
| // pulled out and placed in here. What the generator |
| // class does can probably be added to the Velocity |
| // class and the generator class can probably |
| // be removed all together. |
| populateInitialContext(c); |
| |
| // Feed all the options into the initial |
| // control context so they are available |
| // in the control/worker templates. |
| if (contextProperties != null) |
| { |
| Iterator i = contextProperties.getKeys(); |
| |
| while (i.hasNext()) |
| { |
| String property = (String) i.next(); |
| String value = StringUtils.nullTrim(contextProperties.getString(property)); |
| |
| // Now lets quickly check to see if what |
| // we have is numeric and try to put it |
| // into the context as an Integer. |
| try |
| { |
| c.put(property, new Integer(value)); |
| } |
| catch (NumberFormatException nfe) |
| { |
| // Now we will try to place the value into |
| // the context as a boolean value if it |
| // maps to a valid boolean value. |
| String booleanString = |
| contextProperties.testBoolean(value); |
| |
| if (booleanString != null) |
| { |
| c.put(property, Boolean.valueOf(booleanString)); |
| } |
| else |
| { |
| // We are going to do something special |
| // for properties that have a "file.contents" |
| // suffix: for these properties will pull |
| // in the contents of the file and make |
| // them available in the context. So for |
| // a line like the following in a properties file: |
| // |
| // license.file.contents = license.txt |
| // |
| // We will pull in the contents of license.txt |
| // and make it available in the context as |
| // $license. This should make texen a little |
| // more flexible. |
| if (property.endsWith("file.contents")) |
| { |
| // We need to turn the license file from relative to |
| // absolute, and let Ant help :) |
| value = StringUtils.fileContentsToString( |
| project.resolveFile(value).getCanonicalPath()); |
| |
| property = property.substring( |
| 0, property.indexOf("file.contents") - 1); |
| } |
| |
| c.put(property, value); |
| } |
| } |
| } |
| } |
| |
| writer.write(generator.parse(controlTemplate, c)); |
| writer.flush(); |
| writer.close(); |
| generator.shutdown(); |
| cleanup(); |
| } |
| catch( BuildException e) |
| { |
| throw e; |
| } |
| catch( MethodInvocationException e ) |
| { |
| throw new BuildException( |
| "Exception thrown by '" + e.getReferenceName() + "." + |
| e.getMethodName() +"'" + ERR_MSG_FRAGMENT, |
| e.getWrappedThrowable()); |
| } |
| catch( ParseErrorException e ) |
| { |
| throw new BuildException("Velocity syntax error" + ERR_MSG_FRAGMENT ,e); |
| } |
| catch( ResourceNotFoundException e ) |
| { |
| throw new BuildException("Resource not found" + ERR_MSG_FRAGMENT,e); |
| } |
| catch( Exception e ) |
| { |
| throw new BuildException("Generation failed" + ERR_MSG_FRAGMENT ,e); |
| } |
| } |
| |
| /** |
| * <p>Place useful objects into the initial context.</p> |
| * |
| * <p>TexenTask places <code>Date().toString()</code> into the |
| * context as <code>$now</code>. Subclasses who want to vary the |
| * objects in the context should override this method.</p> |
| * |
| * <p><code>$generator</code> is not put into the context in this |
| * method.</p> |
| * |
| * @param context The context to populate, as retrieved from |
| * {@link #initControlContext()}. |
| * |
| * @throws Exception Error while populating context. The {@link |
| * #execute()} method will catch and rethrow as a |
| * <code>BuildException</code>. |
| */ |
| protected void populateInitialContext(Context context) |
| throws Exception |
| { |
| context.put("now", new Date().toString()); |
| } |
| |
| /** |
| * A hook method called at the end of {@link #execute()} which can |
| * be overridden to perform any necessary cleanup activities (such |
| * as the release of database connections, etc.). By default, |
| * does nothing. |
| * |
| * @exception Exception Problem cleaning up. |
| */ |
| protected void cleanup() |
| throws Exception |
| { |
| } |
| } |