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