rebuilding head
[mir.git] / source / mir / generator / VelocityGenerator.java
1 /*
2  * Copyright (C) 2001, 2002 The Mir-coders group
3  *
4  * This file is part of Mir.
5  *
6  * Mir is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * Mir is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with Mir; if not, write to the Free Software
18  * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
19  *
20  * In addition, as a special exception, The Mir-coders gives permission to link
21  * the code of this program with  any library licensed under the Apache Software License,
22  * The Sun (tm) Java Advanced Imaging library (JAI), The Sun JIMI library
23  * (or with modified versions of the above that use the same license as the above),
24  * and distribute linked combinations including the two.  You must obey the
25  * GNU General Public License in all respects for all of the code used other than
26  * the above mentioned libraries.  If you modify this file, you may extend this
27  * exception to your version of the file, but you are not obligated to do so.
28  * If you do not wish to do so, delete this exception statement from your version.
29  */
30 package mir.generator;
31
32 import java.io.PrintWriter;
33 import java.io.StringWriter;
34 import java.io.File;
35 import java.util.AbstractList;
36 import java.util.Date;
37 import java.util.HashMap;
38 import java.util.Iterator;
39 import java.util.List;
40 import java.util.Map;
41 import java.util.Vector;
42
43 import org.apache.commons.beanutils.MethodUtils;
44 import org.apache.commons.beanutils.PropertyUtils;
45 import org.apache.velocity.Template;
46 import org.apache.velocity.app.VelocityEngine;
47 import org.apache.velocity.context.Context;
48 import org.apache.velocity.exception.ParseErrorException;
49 import org.apache.velocity.exception.ResourceNotFoundException;
50 import org.apache.velocity.runtime.RuntimeServices;
51 import org.apache.velocity.runtime.log.LogSystem;
52 import mir.log.LoggerWrapper;
53 import mir.util.GeneratorFormatAdapters;
54 import mir.util.RewindableIterator;
55
56 public class VelocityGenerator implements Generator {
57   private String templateIdentifier;
58   private VelocityGeneratorLibrary library;
59   private static LoggerWrapper logger = new LoggerWrapper("Generator.velocity");
60
61   /**
62    *
63    * @param aTemplate
64    * @param aLibrary
65    */
66
67   public VelocityGenerator(String aTemplate, VelocityGeneratorLibrary aLibrary) {
68     templateIdentifier = aTemplate;
69     library = aLibrary;
70   }
71
72   /**
73    *
74    * @param anOutputWriter
75    * @param aValues
76    * @param aLogger
77    * @throws GeneratorExc
78    * @throws GeneratorFailure
79    */
80   public void generate(Object anOutputWriter, Map aValues, LoggerWrapper aLogger) throws GeneratorExc, GeneratorFailure {
81     Template template;
82     Context context = makeMapAdapter(aValues);
83     StringWriter stringWriter = new StringWriter();
84
85     try {
86       template = library.engine.getTemplate(templateIdentifier);
87       if (template == null) {
88         throw new GeneratorExc("VelocityGeneratorLibrary: Can't find template " + templateIdentifier);
89       }
90       template.merge(context, stringWriter);
91
92       ( (PrintWriter) anOutputWriter).print(stringWriter.toString());
93     }
94     catch (ResourceNotFoundException t) {
95       throw new GeneratorExc("VelocityGeneratorLibrary: Can't find template " + templateIdentifier);
96     }
97     catch (ParseErrorException t) {
98       ( (PrintWriter) anOutputWriter).print(t.toString());
99     }
100     catch (Throwable t) {
101       throw new GeneratorFailure(t);
102     }
103
104   }
105
106   /**
107    *
108    * @param aMap
109    * @return
110    */
111   private static Context makeMapAdapter(Map aMap)  {
112     return new MapAdapter(aMap);
113   }
114
115   /**
116    *
117    * @param anIterator
118    * @return
119    */
120   private static List makeIteratorAdapter(Iterator anIterator) {
121     return new IteratorAdapter(anIterator);
122   }
123
124   /**
125    *
126    * @param aList
127    * @return
128    */
129   private static List makeListAdapter(List aList) {
130     return new ListAdapter(aList);
131   }
132
133   /**
134    *
135    * @param aFunction
136    * @return
137    */
138   private static Object makeFunctionAdapter(Generator.Function aFunction) {
139     return new FunctionAdapter(aFunction);
140   }
141
142   /**
143    *
144    * @param anObject
145    * @return
146    */
147   private static Object makeBeanAdapter(Object anObject)  {
148     return new BeanAdapter(anObject);
149   }
150
151   /**
152    *
153    * <p>Title: </p>
154    * <p>Description: </p>
155    * <p>Copyright: Copyright (c) 2003</p>
156    * <p>Company: </p>
157    * @author not attributable
158    * @version 1.0
159    */
160   private interface VelocityAdapter {
161     public Object getOriginal();
162   }
163
164   /**
165    *
166    * @param anObject
167    * @return
168    */
169   public static Object unmakeAdapter(Object anObject) {
170     if (anObject instanceof VelocityAdapter) {
171       return ((VelocityAdapter) anObject).getOriginal();
172     }
173     else
174       return anObject;
175   }
176
177   /**
178    *
179    * @param anObject
180    * @return
181    */
182   public static Object makeAdapter(Object anObject) {
183     if (anObject == null)
184       return null;
185
186     if (anObject instanceof Context)
187       return anObject;
188
189     else if (anObject instanceof Generator.Function)
190       return makeFunctionAdapter((Generator.Function) anObject);
191     else if (anObject instanceof Integer)
192       return anObject;
193     else if (anObject instanceof Boolean)
194       return anObject;
195     else if (anObject instanceof String)
196       return anObject;
197     else if (anObject instanceof Map)
198       return makeMapAdapter((Map) anObject);
199     else if (anObject instanceof Iterator)
200       return makeIteratorAdapter((Iterator) anObject);
201     else if (anObject instanceof List)
202       return makeListAdapter(((List) anObject));
203     else if (anObject instanceof Number)
204       return makeAdapter(new GeneratorFormatAdapters.NumberFormatAdapter((Number) anObject));
205     else if (anObject instanceof Date)
206       return makeAdapter(new GeneratorFormatAdapters.DateFormatAdapter((Date) anObject));
207     else
208       return makeBeanAdapter(anObject);
209   }
210
211   /**
212    *
213    * <p>Title: </p>
214    * <p>Description: </p>
215    * <p>Copyright: Copyright (c) 2003</p>
216    * <p>Company: </p>
217    * @author not attributable
218    * @version 1.0
219    */
220   public static class FunctionAdapter implements VelocityAdapter {
221     private Function function;
222
223     public Object getOriginal() {
224       return function;
225     }
226
227     private FunctionAdapter(Function aFunction) {
228       function = aFunction;
229     }
230
231     public Object call(Object aParameters[]) throws GeneratorExc {
232       List parameters = new Vector();
233
234       for (int i = 0; i<aParameters.length; i++) {
235         parameters.add(unmakeAdapter(aParameters[i]));
236       }
237
238       return makeAdapter(function.perform(parameters));
239     }
240
241     public Object call() throws GeneratorExc {
242       return makeAdapter(function.perform(new Vector()));
243     }
244
245     public Object call(Object anObject) throws GeneratorExc {
246       return call(new Object[] { anObject });
247     }
248
249     public Object call(Object anObject1, Object anObject2) throws GeneratorExc {
250       return call(new Object[] { anObject1, anObject2 });
251     }
252
253     public Object call(Object anObject1, Object anObject2, Object anObject3) throws GeneratorExc {
254       return call(new Object[] { anObject1, anObject2, anObject3 });
255     }
256
257     public Object call(Object anObject1, Object anObject2, Object anObject3, Object anObject4) throws GeneratorExc {
258       return call(new Object[] { anObject1, anObject2, anObject3, anObject4 });
259     }
260   }
261
262
263   /**
264    *
265    * <p>Title: </p>
266    * <p>Description: </p>
267    * <p>Copyright: Copyright (c) 2003</p>
268    * <p>Company: </p>
269    * @author not attributable
270    * @version 1.0
271    */
272   private static class MapAdapter implements Context, VelocityAdapter  {
273     private Map map;
274     private Map valuesCache;
275
276     private MapAdapter(Map aMap) {
277       map = aMap;
278       valuesCache = new HashMap();
279     }
280
281     public Object getOriginal() {
282       return map;
283     }
284
285     public boolean containsKey(Object aKey) {
286       return map.containsKey(aKey);
287     }
288
289     public Object get(String aKey) {
290       try {
291         if (!valuesCache.containsKey(aKey)) {
292           Object value = map.get(aKey);
293
294           if (value == null && !map.containsKey(aKey)) {
295             return "no key "+aKey+" available";
296           }
297           else
298             valuesCache.put(aKey, makeAdapter(value));
299         }
300
301         return valuesCache.get(aKey);
302       }
303       catch (Throwable t) {
304         throw new GeneratorFailure(t);
305       }
306     }
307
308     public Object[] getKeys() {
309       return new Object[] {};
310     }
311
312     public Object put(String aKey, Object aValue) {
313       valuesCache.remove(aKey);
314       map.put(aKey, unmakeAdapter(aValue));
315
316       return aValue;
317     }
318
319     public Object remove(java.lang.Object key) {
320       return null;
321     }
322   }
323
324   /**
325    *
326    * <p>Title: </p>
327    * <p>Description: </p>
328    * <p>Copyright: Copyright (c) 2003</p>
329    * <p>Company: </p>
330    * @author not attributable
331    * @version 1.0
332    */
333   private static class IteratorAdapter extends AbstractList implements VelocityAdapter  {
334     private Iterator iterator;
335     private List valuesCache;
336
337     private IteratorAdapter(Iterator anIterator) {
338       iterator = anIterator;
339
340       valuesCache = new Vector();
341
342       if (iterator instanceof RewindableIterator) {
343         ((RewindableIterator) iterator).rewind();
344       }
345     }
346
347     private void getUntil(int anIndex) {
348       while ((anIndex==-1 || valuesCache.size()<=anIndex) && iterator.hasNext())
349       {
350         valuesCache.add(makeAdapter(iterator.next()));
351       }
352     };
353
354     public Object getOriginal() {
355       return iterator;
356     }
357
358     public Object get(int anIndex) {
359       Object result;
360
361       getUntil(anIndex);
362
363       if (anIndex<valuesCache.size())
364       {
365         result = valuesCache.get(anIndex);
366
367         return result;
368       }
369       else
370         throw new RuntimeException( "Iterator out of bounds" );
371     }
372
373     public int size() {
374       getUntil(-1);
375       return valuesCache.size();
376     }
377
378   }
379
380   /**
381    *
382    * <p>Title: </p>
383    * <p>Description: </p>
384    * <p>Copyright: Copyright (c) 2003</p>
385    * <p>Company: </p>
386    * @author not attributable
387    * @version 1.0
388    */
389   private static class ListAdapter extends AbstractList implements VelocityAdapter  {
390     private List list;
391     private List valuesCache;
392
393     private ListAdapter(List aList) {
394       list = aList;
395
396       valuesCache = new Vector();
397     }
398
399     private void getUntil(int anIndex) {
400       while ((anIndex==-1 || valuesCache.size()<=anIndex) && valuesCache.size()<list.size())
401       {
402         valuesCache.add(makeAdapter(list.get(valuesCache.size())));
403       }
404     };
405
406     public Object getOriginal() {
407       return list;
408     }
409
410     public Object get(int anIndex) {
411       Object result;
412
413       getUntil(anIndex);
414
415       if (anIndex<valuesCache.size())
416       {
417         result = valuesCache.get(anIndex);
418
419         return result;
420       }
421       else
422         throw new RuntimeException( "Iterator out of bounds" );
423     }
424
425     public int size() {
426       return list.size();
427     }
428
429   }
430
431   /**
432    *
433    * <p>Title: </p>
434    * <p>Description: </p>
435    * <p>Copyright: Copyright (c) 2003</p>
436    * <p>Company: </p>
437    * @author not attributable
438    * @version 1.0
439    */
440   private static class BeanAdapter implements Context, VelocityAdapter {
441     private Object object;
442
443     public BeanAdapter(Object anObject) {
444       object = anObject;
445     }
446
447     public boolean containsKey(Object key) {
448       return true;
449     }
450
451     public Object getOriginal() {
452       return object;
453     }
454
455     public Object get(String aKey) {
456       try {
457         if (PropertyUtils.isReadable(object, aKey))
458           return makeAdapter(PropertyUtils.getSimpleProperty(object, aKey));
459         else
460           return makeAdapter(MethodUtils.invokeExactMethod(object, "get", aKey));
461       }
462       catch (Throwable t) {
463         throw new GeneratorFailure(t);
464       }
465     }
466
467     public Object[] getKeys() {
468       return new Object[] {};
469     }
470
471     public Object put(String aKey, Object aValue) {
472       try {
473         if (PropertyUtils.isWriteable(object, aKey))
474           PropertyUtils.setSimpleProperty(object, aKey, unmakeAdapter(aValue));
475         else
476           MethodUtils.invokeExactMethod(object, "set", new Object[] {aKey, unmakeAdapter(aValue)});
477
478         return this;
479       }
480       catch (Throwable t) {
481         throw new GeneratorFailure(t);
482       }
483     }
484
485     public Object remove(Object aKey) {
486       throw new RuntimeException("BeanAdapter.remove not supported");
487     }
488   }
489
490   /**
491    *
492    * <p>Title: </p>
493    * <p>Description: </p>
494    * <p>Copyright: Copyright (c) 2003</p>
495    * <p>Company: </p>
496    * @author not attributable
497    * @version 1.0
498    */
499   private static class VelocityLoggerWrapper implements LogSystem {
500     private LoggerWrapper logger;
501
502     public VelocityLoggerWrapper(LoggerWrapper aLogger) {
503       logger = aLogger;
504     }
505
506     public void init(RuntimeServices aRuntimeServices) {
507     }
508
509     public void logVelocityMessage(int aLevel, String aMessage) {
510       switch (aLevel) {
511         case DEBUG_ID:
512           logger.debug(aMessage);
513           break;
514         case ERROR_ID:
515           logger.error(aMessage);
516           break;
517         case INFO_ID:
518           logger.info(aMessage);
519           break;
520         default:
521           logger.warn(aMessage);
522           break;
523       }
524     }
525   }
526
527   /**
528    *
529    * <p>Title: </p>
530    * <p>Description: </p>
531    * <p>Copyright: Copyright (c) 2003</p>
532    * <p>Company: </p>
533    * @author not attributable
534    * @version 1.0
535    */
536   public static class VelocityGeneratorLibrary implements Library {
537     private VelocityEngine engine;
538
539     public VelocityGeneratorLibrary(File aTemplateRoot) throws GeneratorExc, GeneratorFailure {
540       try {
541         engine = new VelocityEngine();
542         try {
543           engine.setProperty(VelocityEngine.RESOURCE_LOADER, "file");
544           engine.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
545           engine.setProperty("file.resource.loader.path", aTemplateRoot.getAbsolutePath());
546           engine.setProperty("file.resource.loader.cache", "true");
547         }
548         catch (Throwable t) {
549           logger.error("Error while constructing library: " + t.toString());
550
551           throw new GeneratorFailure(t);
552         }
553
554         try {
555           engine.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM, new VelocityLoggerWrapper(logger));
556         }
557         catch (Throwable t) {
558           logger.error(VelocityEngine.RUNTIME_LOG_LOGSYSTEM);
559
560         }
561         engine.init();
562       }
563       catch (Throwable t) {
564         t.printStackTrace(logger.asPrintWriter(logger.ERROR_MESSAGE));
565
566         logger.error("Failed to set up a VelocityGeneratorLibrary: " + t.toString());
567         throw new GeneratorFailure(t);
568       }
569     }
570
571     public Generator makeGenerator(String anIdentifier) throws GeneratorExc, GeneratorFailure {
572       return new VelocityGenerator(anIdentifier, this);
573     }
574   }
575
576   /**
577    *
578    * <p>Title: </p>
579    * <p>Description: </p>
580    * <p>Copyright: Copyright (c) 2003</p>
581    * <p>Company: </p>
582    * @author not attributable
583    * @version 1.0
584    */
585   public static class VelocityGeneratorLibraryFactory implements LibraryFactory {
586     private File basePath;
587
588     public VelocityGeneratorLibraryFactory(File aBasePath) {
589       basePath = aBasePath;
590     }
591
592     public Library makeLibrary(String anInitializationString) throws GeneratorExc, GeneratorFailure {
593       return new VelocityGeneratorLibrary(new File(basePath, anInitializationString));
594     };
595   }
596 }