org.apache.flex.forks.velocity.texen.ant.TexenTask.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.flex.forks.velocity.texen.ant.TexenTask.java

Source

package org.apache.flex.forks.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.util.StringTokenizer;
import java.util.Date;
import java.util.Hashtable;
import java.util.Iterator;
import java.util.Map;

import java.io.File;
import java.io.Writer;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.IOException;

import org.apache.tools.ant.BuildException;
import org.apache.tools.ant.Task;
import org.apache.flex.forks.velocity.VelocityContext;
import org.apache.flex.forks.velocity.app.VelocityEngine;
import org.apache.flex.forks.velocity.context.Context;
import org.apache.flex.forks.velocity.texen.Generator;
import org.apache.flex.forks.velocity.util.StringUtils;
import org.apache.flex.forks.velocity.exception.MethodInvocationException;
import org.apache.flex.forks.velocity.exception.ParseErrorException;
import org.apache.flex.forks.velocity.exception.ResourceNotFoundException;
import org.apache.commons.collections.ExtendedProperties;

/**
 * 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: TexenTask.java,v 1.39.4.1 2004/03/03 23:23:07 geirm Exp $
 */
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;

    /**
     * Path separator.
     */
    private String fileSeparator = System.getProperty("file.separator");

    /**
     * [REQUIRED] Set the control template for the
     * generating process.
     */
    public void setControlTemplate(String controlTemplate) {
        this.controlTemplate = controlTemplate;
    }

    /**
     * Get the control template for the
     * generating process.
     */
    public String getControlTemplate() {
        return controlTemplate;
    }

    /**
     * [REQUIRED] Set the path where Velocity will look
     * for templates using the file template
     * loader.
     */

    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.
     */
    public String getTemplatePath() {
        return templatePath;
    }

    /**
     * [REQUIRED] Set the output directory. It will be
     * created if it doesn't exist.
     */
    public void setOutputDirectory(File outputDirectory) {
        try {
            this.outputDirectory = outputDirectory.getCanonicalPath();
        } catch (java.io.IOException ioe) {
            throw new BuildException(ioe);
        }
    }

    /**
     * Get the output directory.
     */
    public String getOutputDirectory() {
        return outputDirectory;
    }

    /**
     * [REQUIRED] Set the output file for the
     * generation process.
     */
    public void setOutputFile(String outputFile) {
        this.outputFile = outputFile;
    }

    /**
     * Set the output encoding.
     */
    public void setOutputEncoding(String outputEncoding) {
        this.outputEncoding = outputEncoding;
    }

    /**
     * Set the input (template) encoding.
     */
    public void setInputEncoding(String inputEncoding) {
        this.inputEncoding = inputEncoding;
    }

    /**
     * Get the output file for the
     * generation process.
     */
    public String getOutputFile() {
        return outputFile;
    }

    /**
     * Set the context properties that will be
     * fed into the initial context be the
     * generating process starts.
     */
    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 (Exception 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;
                }
            }

            Iterator j = source.getKeys();

            while (j.hasNext()) {
                String name = (String) j.next();
                String value = source.getString(name);
                contextProperties.setProperty(name, value);
            }
        }
    }

    /**
     * Get the context properties that will be
     * fed into the initial context be the
     * generating process starts.
     */
    public ExtendedProperties getContextProperties() {
        return contextProperties;
    }

    /**
     * Set the use of the classpath in locating templates
     *
     * @param boolean true means the classpath will be used.
     */
    public void setUseClasspath(boolean useClasspath) {
        this.useClasspath = useClasspath;
    }

    /**
     * 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(ve.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.flex.forks.velocity.runtime.resource.loader.ClasspathResourceLoader");

                ve.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER + ".cache", "false");

                ve.setProperty("classpath." + VelocityEngine.RESOURCE_LOADER + ".modificationCheckInterval", "2");
            }

            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 = 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, new Boolean(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 {
    }
}