You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@velocity.apache.org by jv...@apache.org on 2002/03/04 02:52:34 UTC

cvs commit: jakarta-velocity/src/java/org/apache/velocity/texen/ant Texen.java

jvanzyl     02/03/03 17:52:34

  Added:       src/java/org/apache/velocity/texen/ant Texen.java
  Log:
  Allow the use of Texen as a bean so that it can eventually be utililized
  from the command line and from GUIs. The impetus came from Maven where I
  want to use Texen from the command line to generate the build system for
  a project. I am not fully ready to replace the existing Ant task even
  though the bean is working correctly with the TaskAdapter in Ant. I
  need to clean up some of the exception reporting but this is enough
  to allow me to get going in Maven. This bean will not replace the
  task until thorough testing.
  
  Revision  Changes    Path
  1.1                  jakarta-velocity/src/java/org/apache/velocity/texen/ant/Texen.java
  
  Index: Texen.java
  ===================================================================
  package org.apache.velocity.texen.ant;
  
  /*
   * The Apache Software License, Version 1.1
   *
   * Copyright (c) 2001 The Apache Software Foundation.  All rights
   * reserved.
   *
   * Redistribution and use in source and binary forms, with or without
   * modification, are permitted provided that the following conditions
   * are met:
   *
   * 1. Redistributions of source code must retain the above copyright
   *    notice, this list of conditions and the following disclaimer.
   *
   * 2. Redistributions in binary form must reproduce the above copyright
   *    notice, this list of conditions and the following disclaimer in
   *    the documentation and/or other materials provided with the
   *    distribution.
   *
   * 3. The end-user documentation included with the redistribution, if
   *    any, must include the following acknowlegement:
   *       "This product includes software developed by the
   *        Apache Software Foundation (http://www.apache.org/)."
   *    Alternately, this acknowlegement may appear in the software itself,
   *    if and wherever such third-party acknowlegements normally appear.
   *
   * 4. The names "The Jakarta Project", "Velocity", and "Apache Software
   *    Foundation" must not be used to endorse or promote products derived
   *    from this software without prior written permission. For written
   *    permission, please contact apache@apache.org.
   *
   * 5. Products derived from this software may not be called "Apache"
   *    nor may "Apache" appear in their names without prior written
   *    permission of the Apache Group.
   *
   * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
   * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
   * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
   * DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
   * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
   * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
   * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
   * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
   * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
   * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
   * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
   * SUCH DAMAGE.
   * ====================================================================
   *
   * This software consists of voluntary contributions made by many
   * individuals on behalf of the Apache Software Foundation.  For more
   * information on the Apache Software Foundation, please see
   * <http://www.apache.org/>.
   */
  
  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.Project;
  
  import org.apache.velocity.VelocityContext;
  import org.apache.velocity.app.Velocity;
  import org.apache.velocity.context.Context;
  import org.apache.velocity.texen.Generator;
  import org.apache.velocity.util.StringUtils;
  import org.apache.velocity.exception.MethodInvocationException;
  import org.apache.velocity.exception.ParseErrorException;
  import org.apache.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: Texen.java,v 1.1 2002/03/04 01:52:34 jvanzyl Exp $
   */
  public class Texen
  {
      /**
       * 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;
      
      /**
       * Reference to the context properties that is held
       * until processing in the execute() method. We do this
       * now so that the Ant TaskAdapter can set the project
       * which is needed for correct path processing when
       * this bean is used as an ant task.
       */
      protected String contextPropertiesFile;
  
      /**
       * 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");
  
      /**
       * Ant project that is populated by the Ant TaskAdapter when
       * this bean is run as an Ant Task.
       */
      protected Project project;
  
      /**
       * [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)
      {
          this.templatePath = templatePath;
      }        
      
      /**
       * Process the templatePath. We hold off processing so that
       * when run as an Ant Task the project will be set, and the
       * project is required for correct path resolving.
       */
      protected void processTemplatePath(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 = resolveFile(st.nextToken());
              resolvedPath.append(fullPath.getCanonicalPath());
              if ( st.hasMoreTokens() )
              {
                  resolvedPath.append(",");
              }
          }
          this.templatePath = resolvedPath.toString();
       }
  
      /**
       * 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)
          throws Exception
      {
          try
          {
              this.outputDirectory = outputDirectory.getCanonicalPath();
          }
          catch (IOException ioe)
          {
              throw new Exception(
                  "There is a problem with the outputDirectory: " + 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)
      {
          this.contextPropertiesFile = file;
      }
  
      /**
       * Process the templatePath. We hold off processing so that
       * when run as an Ant Task the project will be set, and the
       * project is required for correct path resolving.
       */
      protected void processContextProperties( String file )
          throws Exception
      {
          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 = 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 Exception("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;
      }        
  
      /**
       * Set the Ant project. Used when this bean is adapted
       * by the Ant TaskAdapter.
       */
      public void setProject(Project project)
      {
          this.project = project;
      }
      
      /**
       * Get the Ant project which will be non-null if
       * this bean is used as a Task and adapted by the
       * Ant TaskAdapter.
       */
      public Project getProject()
      {
          return project;
      }        
  
      /**
       * 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 Exception
      {
          // Make sure the template path is set.
          if (templatePath == null && useClasspath == false)
          {
              throw new Exception(
                  "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 Exception("The control template needs to be defined!");
          }            
  
          // Make sure the output directory is set.
          if (outputDirectory == null)
          {
              throw new Exception("The output directory needs to be defined!");
          }            
          
          // Make sure there is an output file.
          if (outputFile == null)
          {
              throw new Exception("The output file needs to be defined!");
          }            
  
          // Process the context properties
          if (contextPropertiesFile != null)
          {
              processContextProperties(contextPropertiesFile);
          }            
  
          try
          {
              // Setup the Velocity Runtime.
              if (templatePath != null)
              {
                  processTemplatePath(templatePath);            
              
                  log("Using templatePath: " + templatePath);
                  Velocity.setProperty(
                      Velocity.FILE_RESOURCE_LOADER_PATH, templatePath);
              }
              
              if (useClasspath)
              {
              	log("Using classpath");
                  Velocity.addProperty(
                      Velocity.RESOURCE_LOADER, "classpath");
              
                  Velocity.setProperty(
                      "classpath." + Velocity.RESOURCE_LOADER + ".class",
                          "org.apache.velocity.runtime.resource.loader.ClasspathResourceLoader");
  
                  Velocity.setProperty(
                      "classpath." + Velocity.RESOURCE_LOADER + 
                          ".cache", "false");
  
                  Velocity.setProperty(
                      "classpath." + Velocity.RESOURCE_LOADER + 
                          ".modificationCheckInterval", "2");
              }
              
              Velocity.init();
  
              // Create the text generator.
              Generator generator = Generator.getInstance();
              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);
              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(   
                                      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( MethodInvocationException e )
          {
              throw new Exception(
                  "Exception thrown by '" + e.getReferenceName() + "." + 
                      e.getMethodName() +"'" + ERR_MSG_FRAGMENT);
          }       
          catch( ParseErrorException e )
          {
              throw new Exception("Velocity syntax error" + ERR_MSG_FRAGMENT);
          }        
          catch( ResourceNotFoundException e )
          {
              throw new Exception("Resource not found" + ERR_MSG_FRAGMENT);
          }
          catch( Exception e )
          {
              throw new Exception("Generation failed" + ERR_MSG_FRAGMENT);
          }
      }
  
      /**
       * <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
      {
      }
      
      /**
       * Log a message. Use the Ant logging system if being
       * used as an Ant Task.
       */
      private void log(String message)
      {
          if (project != null)
          {
              project.log(message);
          }
          else
          {
              System.out.println(message);
          }            
      }        
  
      /**
       * Resolve a file. Use the baseDir of the project if
       * being run as an Ant Task. A couple methods
       */
      private File resolveFile(String file)
          throws Exception
      {
          if (project != null)
          {
              return project.resolveFile(file);
          }
          else
          {
              return new File(file);
          }            
      }
  }
  
  
  

--
To unsubscribe, e-mail:   <ma...@jakarta.apache.org>
For additional commands, e-mail: <ma...@jakarta.apache.org>