View Javadoc

1   package org.apache.texen;
2   
3   /*
4    * Licensed to the Apache Software Foundation (ASF) under one
5    * or more contributor license agreements.  See the NOTICE file
6    * distributed with this work for additional information
7    * regarding copyright ownership.  The ASF licenses this file
8    * to you under the Apache License, Version 2.0 (the
9    * "License"); you may not use this file except in compliance
10   * with the License.  You may obtain a copy of the License at
11   *
12   *   http://www.apache.org/licenses/LICENSE-2.0
13   *
14   * Unless required by applicable law or agreed to in writing,
15   * software distributed under the License is distributed on an
16   * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
17   * KIND, either express or implied.  See the License for the
18   * specific language governing permissions and limitations
19   * under the License.    
20   */
21  
22  import java.io.File;
23  import java.io.InputStream;
24  import java.io.FileInputStream;
25  import java.io.BufferedInputStream;
26  import java.io.Writer;
27  import java.io.FileWriter;
28  import java.io.IOException;
29  import java.io.StringWriter;
30  import java.io.OutputStreamWriter;
31  import java.io.BufferedWriter;
32  import java.io.FileOutputStream;
33  
34  import java.util.Enumeration;
35  import java.util.Hashtable;
36  import java.util.Iterator;
37  import java.util.Properties;
38  
39  import org.apache.velocity.Template;
40  import org.apache.velocity.context.Context;
41  import org.apache.velocity.VelocityContext;
42  import org.apache.velocity.app.VelocityEngine;
43  import org.apache.velocity.util.ClassUtils;
44  
45  /**
46   * A text/code generator class
47   *
48   * @author <a href="mailto:leon@opticode.co.za">Leon Messerschmidt</a>
49   * @author <a href="mailto:jvanzyl@apache.org">Jason van Zyl</a>
50   * @version $Id: Generator.java 525620 2007-04-04 21:54:47Z nbubna $
51   */
52  public class Generator
53  {
54      /**
55       * Where the texen output will placed.
56       */
57      public static final String OUTPUT_PATH = "output.path";
58  
59      /**
60       * Where the velocity templates live.
61       */
62      public static final String TEMPLATE_PATH = "template.path";
63  
64      /**
65       * Default properties file used for controlling the
66       * tools placed in the context.
67       */
68      private static final String DEFAULT_TEXEN_PROPERTIES =
69          "org/apache/texen/defaults/texen.properties";
70  
71      /**
72       * Default properties used by texen.
73       */
74      private Properties props = new Properties();
75  
76      /**
77       * Context used for generating the texen output.
78       */
79      private Context controlContext;
80  
81      /**
82       * Keep track of the file writers used for outputting
83       * to files. If we come across a file writer more
84       * then once then the additional output will be
85       * appended to the file instead of overwritting
86       * the contents.
87       */
88      private Hashtable writers = new Hashtable();
89  
90      /**
91       * The generator tools used for creating additional
92       * output withing the control template. This could
93       * use some cleaning up.
94       */
95      private static Generator instance = new Generator();
96  
97      /**
98       * This is the encoding for the output file(s).
99       */
100     protected String outputEncoding;
101 
102     /**
103      * This is the encoding for the input file(s)
104      * (templates).
105      */
106     protected String inputEncoding;
107 
108     /**
109      * Velocity engine.
110      */
111     protected VelocityEngine ve;
112 
113     /**
114      * Default constructor.
115      */
116     private Generator()
117     {
118         setDefaultProps();
119     }
120 
121     /**
122      * Create a new generator object with default properties.
123      *
124      * @return Generator generator used in the control context.
125      */
126     public static Generator getInstance()
127     {
128         return instance;
129     }
130 
131     /**
132      * Set the velocity engine.
133      * @param ve
134      */
135     public void setVelocityEngine(VelocityEngine ve)
136     {
137         this.ve = ve;
138     }
139 
140     /**
141      * Create a new generator object with properties loaded from
142      * a file.  If the file does not exist or any other exception
143      * occurs during the reading operation the default properties
144      * are used.
145      *
146      * @param propFile properties used to help populate the control context.
147      */
148     public Generator (String propFile)
149     {
150         try
151         {
152             BufferedInputStream bi = null;
153             try
154             {
155                 bi = new BufferedInputStream (new FileInputStream (propFile));
156                 props.load (bi);
157             }
158             finally
159             {
160                 if (bi != null)
161                 {
162                     bi.close();
163                 }
164             }
165         }
166         catch (IOException e)
167         {
168             System.err.println("Could not load " + propFile
169                     + ", falling back to defaults. ("
170                     + e.getMessage() + ")");
171             /*
172              * If something goes wrong we use default properties
173              */
174             setDefaultProps();
175         }
176     }
177 
178     /**
179      * Create a new Generator object with a given property
180      * set. The property set will be duplicated.
181      *
182      * @param props properties object to help populate the control context.
183      */
184     public Generator (Properties props)
185     {
186         this.props = (Properties)props.clone();
187     }
188 
189     /**
190      * Set default properties.
191      */
192     protected void setDefaultProps()
193     {
194         ClassLoader classLoader = VelocityEngine.class.getClassLoader();
195         try
196         {
197             InputStream inputStream = null;
198             try
199             {
200                 inputStream = classLoader.getResourceAsStream(
201                     DEFAULT_TEXEN_PROPERTIES);
202 
203                 props.load( inputStream );
204             }
205             finally
206             {
207                 if (inputStream != null)
208                 {
209                     inputStream.close();
210                 }
211             }
212         }
213         catch (IOException ioe)
214         {
215             System.err.println("Cannot get default properties: " + ioe.getMessage());
216         }
217     }
218 
219     /**
220      * Set the template path, where Texen will look
221      * for Velocity templates.
222      *
223      * @param templatePath template path for velocity templates.
224      */
225     public void setTemplatePath(String templatePath)
226     {
227         props.put(TEMPLATE_PATH, templatePath);
228     }
229 
230     /**
231      * Get the template path.
232      *
233      * @return String template path for velocity templates.
234      */
235     public String getTemplatePath()
236     {
237         return props.getProperty(TEMPLATE_PATH);
238     }
239 
240     /**
241      * Set the output path for the generated
242      * output.
243      * @param outputPath
244      */
245     public void setOutputPath(String outputPath)
246     {
247         props.put(OUTPUT_PATH, outputPath);
248     }
249 
250     /**
251      * Get the output path for the generated
252      * output.
253      *
254      * @return String output path for texen output.
255      */
256     public String getOutputPath()
257     {
258         return props.getProperty(OUTPUT_PATH);
259     }
260 
261     /**
262      * Set the output encoding.
263      * @param outputEncoding
264      */
265     public void setOutputEncoding(String outputEncoding)
266     {
267         this.outputEncoding = outputEncoding;
268     }
269 
270     /**
271      * Set the input (template) encoding.
272      * @param inputEncoding
273      */
274     public void setInputEncoding(String inputEncoding)
275     {
276         this.inputEncoding = inputEncoding;
277     }
278 
279     /**
280      * Returns a writer, based on encoding and path.
281      *
282      * @param path      path to the output file
283      * @param encoding  output encoding
284      * @return A Writer for this generator.
285      * @throws Exception
286      */
287     public Writer getWriter(String path, String encoding) throws Exception {
288         Writer writer;
289         if (encoding == null || encoding.length() == 0 || encoding.equals("8859-1") || encoding.equals("8859_1")) {
290             writer = new FileWriter(path);
291         }
292         else
293         {
294             writer = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(path), encoding));
295         }
296         return writer;
297     }
298 
299     /**
300      * Returns a template, based on encoding and path.
301      *
302      * @param templateName  name of the template
303      * @param encoding      template encoding
304      * @return A Template.
305      * @throws Exception
306      */
307     public Template getTemplate(String templateName, String encoding) throws Exception {
308         Template template;
309         if (encoding == null || encoding.length() == 0 || encoding.equals("8859-1") || encoding.equals("8859_1")) {
310             template = ve.getTemplate(templateName);
311         }
312         else {
313             template = ve.getTemplate(templateName, encoding);
314         }
315         return template;
316     }
317 
318     /**
319      * Parse an input and write the output to an output file.  If the
320      * output file parameter is null or an empty string the result is
321      * returned as a string object.  Otherwise an empty string is returned.
322      *
323      * @param inputTemplate input template
324      * @param outputFile output file
325      * @return The parsed file.
326      * @throws Exception
327      */
328     public String parse (String inputTemplate, String outputFile)
329         throws Exception
330     {
331         return parse(inputTemplate, outputFile, null, null);
332     }
333 
334     /**
335      * Parse an input and write the output to an output file.  If the
336      * output file parameter is null or an empty string the result is
337      * returned as a string object.  Otherwise an empty string is returned.
338      * You can add objects to the context with the objs Hashtable.
339      *
340      * @param inputTemplate input template
341      * @param outputFile output file
342      * @param objectID id for object to be placed in the control context
343      * @param object object to be placed in the context
344      * @return String generated output from velocity
345      * @throws Exception
346      */
347     public String parse (String inputTemplate,
348                          String outputFile,
349                          String objectID,
350                          Object object)
351         throws Exception
352     {
353         return parse(inputTemplate, null, outputFile, null, objectID, object);
354     }
355     /**
356      * Parse an input and write the output to an output file.  If the
357      * output file parameter is null or an empty string the result is
358      * returned as a string object.  Otherwise an empty string is returned.
359      * You can add objects to the context with the objs Hashtable.
360      *
361      * @param inputTemplate input template
362      * @param inputEncoding template encoding
363      * @param outputFile output file
364      * @param outputEncoding outputEncoding encoding of output file
365      * @param objectID id for object to be placed in the control context
366      * @param object object to be placed in the context
367      * @return String generated output from velocity
368      * @throws Exception
369      */
370     public String parse (String inputTemplate,
371                          String inputEncoding,
372                          String outputFile,
373                          String outputEncoding,
374                          String objectID,
375                          Object object)
376         throws Exception
377     {
378         if (objectID != null && object != null)
379         {
380             controlContext.put(objectID, object);
381         }
382 
383         Template template = getTemplate(inputTemplate, inputEncoding != null ? inputEncoding : this.inputEncoding);
384 
385         if (outputFile == null || outputFile.equals(""))
386         {
387             StringWriter sw = new StringWriter();
388             template.merge (controlContext,sw);
389             return sw.toString();
390         }
391         else
392         {
393             Writer writer = null;
394 
395             if (writers.get(outputFile) == null)
396             {
397                 /*
398                  * We have never seen this file before so create
399                  * a new file writer for it.
400                  */
401                 writer = getWriter(
402                             getOutputPath() + File.separator + outputFile,
403                             outputEncoding != null ? outputEncoding : this.outputEncoding
404                          );
405 
406                 /*
407                  * Place the file writer in our collection
408                  * of file writers.
409                  */
410                 writers.put(outputFile, writer);
411             }
412             else
413             {
414                 writer = (Writer) writers.get(outputFile);
415             }
416 
417             VelocityContext vc = new VelocityContext( controlContext );
418             template.merge (vc,writer);
419 
420             // commented because it is closed in shutdown();
421             //fw.close();
422 
423             return "";
424         }
425     }
426 
427     /**
428      * Parse the control template and merge it with the control
429      * context. This is the starting point in texen.
430      *
431      * @param controlTemplate control template
432      * @param controlContext control context
433      * @return String generated output
434      * @throws Exception
435      */
436     public String parse (String controlTemplate, Context controlContext)
437         throws Exception
438     {
439         this.controlContext = controlContext;
440         fillContextDefaults(this.controlContext);
441         fillContextProperties(this.controlContext);
442 
443         Template template = getTemplate(controlTemplate, inputEncoding);
444         StringWriter sw = new StringWriter();
445         template.merge (controlContext,sw);
446 
447         return sw.toString();
448     }
449 
450 
451     /**
452      * Create a new context and fill it with the elements of the
453      * objs Hashtable.  Default objects and objects that comes from
454      * the properties of this Generator object is also added.
455      *
456      * @param objs objects to place in the control context
457      * @return Context context filled with objects
458      */
459     protected Context getContext (Hashtable objs)
460     {
461         fillContextHash (controlContext,objs);
462         return controlContext;
463     }
464 
465     /**
466      * Add all the contents of a Hashtable to the context.
467      *
468      * @param context context to fill with objects
469      * @param objs source of objects
470      */
471     protected void fillContextHash (Context context, Hashtable objs)
472     {
473         Enumeration enumeration = objs.keys();
474         while (enumeration.hasMoreElements())
475         {
476             String key = enumeration.nextElement().toString();
477             context.put (key, objs.get(key));
478         }
479     }
480 
481     /**
482      * Add properties that will aways be in the context by default
483      *
484      * @param context control context to fill with default values.
485      */
486     protected void fillContextDefaults (Context context)
487     {
488         context.put ("generator", instance);
489         context.put ("outputDirectory", getOutputPath());
490     }
491 
492     /**
493      * Add objects to the context from the current properties.
494      *
495      * @param context control context to fill with objects
496      *                that are specified in the default.properties
497      *                file
498      */
499     protected void fillContextProperties (Context context)
500     {
501         Enumeration enumeration = props.propertyNames();
502 
503         while (enumeration.hasMoreElements())
504         {
505             String nm = (String) enumeration.nextElement();
506             if (nm.startsWith ("context.objects."))
507             {
508 
509                 String contextObj = props.getProperty (nm);
510                 int colon = nm.lastIndexOf ('.');
511                 String contextName = nm.substring (colon+1);
512 
513                 try
514                 {
515                     Object o = ClassUtils.getNewInstance(contextObj);
516                     context.put (contextName,o);
517                 }
518                 catch (Exception e)
519                 {
520                     e.printStackTrace();
521                     //TO DO: Log Something Here
522                 }
523             }
524         }
525     }
526 
527     /**
528      * Properly shut down the generator, right now
529      * this is simply flushing and closing the file
530      * writers that we have been holding on to.
531      */
532     public void shutdown()
533     {
534         Iterator iterator = writers.values().iterator();
535 
536         while(iterator.hasNext())
537         {
538             Writer writer = (Writer) iterator.next();
539 
540             try
541             {
542                 writer.flush();
543             }
544             catch (IOException e)
545             {
546                 /* do nothing */
547             }
548 
549             try
550             {
551                 writer.close();
552             }
553             catch (IOException e)
554             {
555                 /* do nothing */
556             }
557         }
558         // clear the file writers cache
559         writers.clear();
560     }
561 }