1 package org.apache.velocity.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 463298 2006-10-12 16:10:32Z henning $
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/velocity/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 }