340aa4bc7db568a72dc49ad33652476ee66ed2e4
[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.File;
33 import java.io.PrintWriter;
34 import java.io.StringWriter;
35 import java.util.AbstractList;
36 import java.util.ArrayList;
37 import java.util.Date;
38 import java.util.HashMap;
39 import java.util.Iterator;
40 import java.util.List;
41 import java.util.Map;
42
43 import mir.log.LoggerWrapper;
44 import mir.util.GeneratorFormatAdapters;
45 import mir.util.RewindableIterator;
46
47 import org.apache.commons.beanutils.MethodUtils;
48 import org.apache.commons.beanutils.PropertyUtils;
49 import org.apache.velocity.Template;
50 import org.apache.velocity.app.VelocityEngine;
51 import org.apache.velocity.context.Context;
52 import org.apache.velocity.exception.ParseErrorException;
53 import org.apache.velocity.exception.ResourceNotFoundException;
54 import org.apache.velocity.runtime.RuntimeServices;
55 import org.apache.velocity.runtime.log.LogSystem;
56
57 public class VelocityGenerator implements Generator {
58   private String templateIdentifier;
59   private VelocityGeneratorLibrary library;
60   private static LoggerWrapper logger = new LoggerWrapper("Generator.velocity");
61
62   /**
63    *
64    * @param aTemplate
65    * @param aLibrary
66    */
67
68   public VelocityGenerator(String aTemplate, VelocityGeneratorLibrary aLibrary) {
69     templateIdentifier = aTemplate;
70     library = aLibrary;
71   }
72
73   /**
74    *
75    * @param anOutputWriter
76    * @param aValues
77    * @param aLogger
78    * @throws GeneratorExc
79    * @throws GeneratorFailure
80    */
81   public void generate(Object anOutputWriter, Map aValues, LoggerWrapper aLogger) throws GeneratorExc, GeneratorFailure {
82     Template template;
83     Context context = makeMapAdapter(aValues);
84     StringWriter stringWriter = new StringWriter();
85
86     try {
87       template = library.engine.getTemplate(templateIdentifier);
88       if (template == null) {
89         throw new GeneratorExc("VelocityGeneratorLibrary: Can't find template " + templateIdentifier);
90       }
91       template.merge(context, stringWriter);
92
93       ( (PrintWriter) anOutputWriter).print(stringWriter.toString());
94     }
95     catch (ResourceNotFoundException t) {
96       throw new GeneratorExc("VelocityGeneratorLibrary: Can't find template " + templateIdentifier);
97     }
98     catch (ParseErrorException t) {
99       ( (PrintWriter) anOutputWriter).print(t.toString());
100     }
101     catch (Throwable t) {
102       throw new GeneratorFailure(t);
103     }
104
105   }
106
107   /**
108    *
109    * @param aMap
110    * @return
111    */
112   private static Context makeMapAdapter(Map aMap)  {
113     return new MapAdapter(aMap);
114   }
115
116   /**
117    *
118    * @param anIterator
119    * @return
120    */
121   private static List makeIteratorAdapter(Iterator anIterator) {
122     return new IteratorAdapter(anIterator);
123   }
124
125   /**
126    *
127    * @param aList
128    * @return
129    */
130   private static List makeListAdapter(List aList) {
131     return new ListAdapter(aList);
132   }
133
134   /**
135    *
136    * @param aFunction
137    * @return
138    */
139   private static Object makeFunctionAdapter(Generator.Function aFunction) {
140     return new FunctionAdapter(aFunction);
141   }
142
143   /**
144    *
145    * @param anObject
146    * @return
147    */
148   private static Object makeBeanAdapter(Object anObject)  {
149     return new BeanAdapter(anObject);
150   }
151
152   /**
153    *
154    * <p>Title: </p>
155    * <p>Description: </p>
156    * <p>Copyright: Copyright (c) 2003</p>
157    * <p>Company: </p>
158    * @author not attributable
159    * @version 1.0
160    */
161   private interface VelocityAdapter {
162     public Object getOriginal();
163   }
164
165   /**
166    *
167    * @param anObject
168    * @return
169    */
170   public static Object unmakeAdapter(Object anObject) {
171     if (anObject instanceof VelocityAdapter) {
172       return ((VelocityAdapter) anObject).getOriginal();
173     }
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 ArrayList();
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 ArrayList()));
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 ArrayList();
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                         throw new RuntimeException( "Iterator out of bounds" );
370     }
371
372     public int size() {
373       getUntil(-1);
374       return valuesCache.size();
375     }
376
377   }
378
379   /**
380    *
381    * <p>Title: </p>
382    * <p>Description: </p>
383    * <p>Copyright: Copyright (c) 2003</p>
384    * <p>Company: </p>
385    * @author not attributable
386    * @version 1.0
387    */
388   private static class ListAdapter extends AbstractList implements VelocityAdapter  {
389     private List list;
390     private List valuesCache;
391
392     private ListAdapter(List aList) {
393       list = aList;
394
395       valuesCache = new ArrayList();
396     }
397
398     private void getUntil(int anIndex) {
399       while ((anIndex==-1 || valuesCache.size()<=anIndex) && valuesCache.size()<list.size())
400       {
401         valuesCache.add(makeAdapter(list.get(valuesCache.size())));
402       }
403     }
404
405     public Object getOriginal() {
406       return list;
407     }
408
409     public Object get(int anIndex) {
410       Object result;
411
412       getUntil(anIndex);
413
414       if (anIndex<valuesCache.size())
415       {
416         result = valuesCache.get(anIndex);
417
418         return result;
419       }
420                         throw new RuntimeException( "Iterator out of bounds" );
421     }
422
423     public int size() {
424       return list.size();
425     }
426
427   }
428
429   /**
430    *
431    * <p>Title: </p>
432    * <p>Description: </p>
433    * <p>Copyright: Copyright (c) 2003</p>
434    * <p>Company: </p>
435    * @author not attributable
436    * @version 1.0
437    */
438   private static class BeanAdapter implements Context, VelocityAdapter {
439     private Object object;
440
441     public BeanAdapter(Object anObject) {
442       object = anObject;
443     }
444
445     public boolean containsKey(Object key) {
446       return true;
447     }
448
449     public Object getOriginal() {
450       return object;
451     }
452
453     public Object get(String aKey) {
454       try {
455         if (PropertyUtils.isReadable(object, aKey))
456           return makeAdapter(PropertyUtils.getSimpleProperty(object, aKey));
457         else
458           return makeAdapter(MethodUtils.invokeExactMethod(object, "get", aKey));
459       }
460       catch (Throwable t) {
461         throw new GeneratorFailure(t);
462       }
463     }
464
465     public Object[] getKeys() {
466       return new Object[] {};
467     }
468
469     public Object put(String aKey, Object aValue) {
470       try {
471         if (PropertyUtils.isWriteable(object, aKey))
472           PropertyUtils.setSimpleProperty(object, aKey, unmakeAdapter(aValue));
473         else
474           MethodUtils.invokeExactMethod(object, "set", new Object[] {aKey, unmakeAdapter(aValue)});
475
476         return this;
477       }
478       catch (Throwable t) {
479         throw new GeneratorFailure(t);
480       }
481     }
482
483     public Object remove(Object aKey) {
484       throw new RuntimeException("BeanAdapter.remove not supported");
485     }
486   }
487
488   /**
489    *
490    * <p>Title: </p>
491    * <p>Description: </p>
492    * <p>Copyright: Copyright (c) 2003</p>
493    * <p>Company: </p>
494    * @author not attributable
495    * @version 1.0
496    */
497   private static class VelocityLoggerWrapper implements LogSystem {
498    
499     public VelocityLoggerWrapper(LoggerWrapper aLogger) {
500       logger = aLogger;
501     }
502
503     public void init(RuntimeServices aRuntimeServices) {
504     }
505
506     public void logVelocityMessage(int aLevel, String aMessage) {
507       switch (aLevel) {
508         case DEBUG_ID:
509           logger.debug(aMessage);
510           break;
511         case ERROR_ID:
512           logger.error(aMessage);
513           break;
514         case INFO_ID:
515           logger.info(aMessage);
516           break;
517         default:
518           logger.warn(aMessage);
519           break;
520       }
521     }
522   }
523
524   /**
525    *
526    * <p>Title: </p>
527    * <p>Description: </p>
528    * <p>Copyright: Copyright (c) 2003</p>
529    * <p>Company: </p>
530    * @author not attributable
531    * @version 1.0
532    */
533   public static class VelocityGeneratorLibrary implements Library {
534     private VelocityEngine engine;
535
536     public VelocityGeneratorLibrary(File aTemplateRoot) throws GeneratorExc, GeneratorFailure {
537       try {
538         engine = new VelocityEngine();
539         try {
540           engine.setProperty(VelocityEngine.RESOURCE_LOADER, "file");
541           engine.setProperty("file.resource.loader.class", "org.apache.velocity.runtime.resource.loader.FileResourceLoader");
542           engine.setProperty("file.resource.loader.path", aTemplateRoot.getAbsolutePath());
543           engine.setProperty("file.resource.loader.cache", "true");
544         }
545         catch (Throwable t) {
546           logger.error("Error while constructing library: " + t.toString());
547
548           throw new GeneratorFailure(t);
549         }
550
551         try {
552           engine.setProperty(VelocityEngine.RUNTIME_LOG_LOGSYSTEM, new VelocityLoggerWrapper(logger));
553         }
554         catch (Throwable t) {
555           logger.error(VelocityEngine.RUNTIME_LOG_LOGSYSTEM);
556
557         }
558         engine.init();
559       }
560       catch (Throwable t) {
561         logger.error("Failed to set up a VelocityGeneratorLibrary: " + t.toString());
562         throw new GeneratorFailure(t);
563       }
564     }
565
566     public Generator makeGenerator(String anIdentifier) throws GeneratorExc, GeneratorFailure {
567       return new VelocityGenerator(anIdentifier, this);
568     }
569   }
570
571   /**
572    *
573    * <p>Title: </p>
574    * <p>Description: </p>
575    * <p>Copyright: Copyright (c) 2003</p>
576    * <p>Company: </p>
577    * @author not attributable
578    * @version 1.0
579    */
580   public static class VelocityGeneratorLibraryFactory implements LibraryFactory {
581     private File basePath;
582
583     public VelocityGeneratorLibraryFactory(File aBasePath) {
584       basePath = aBasePath;
585     }
586
587     public Library makeLibrary(String anInitializationString) throws GeneratorExc, GeneratorFailure {
588       return new VelocityGeneratorLibrary(new File(basePath, anInitializationString));
589     }
590   }
591 }