795807a713ffc56626b87d9b44260a707b86da14
[mir.git] / source / mir / generator / FreemarkerGenerator.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.util.ArrayList;
35 import java.util.Date;
36 import java.util.HashMap;
37 import java.util.Iterator;
38 import java.util.List;
39 import java.util.Map;
40 import java.util.Collection;
41
42 import mir.log.LoggerWrapper;
43 import mir.util.GeneratorFormatAdapters;
44 import mir.util.RewindableIterator;
45
46 import org.apache.commons.beanutils.MethodUtils;
47 import org.apache.commons.beanutils.PropertyUtils;
48
49 import freemarker.template.FileTemplateCache;
50 import freemarker.template.SimpleScalar;
51 import freemarker.template.Template;
52 import freemarker.template.TemplateHashModel;
53 import freemarker.template.TemplateListModel;
54 import freemarker.template.TemplateMethodModel;
55 import freemarker.template.TemplateModel;
56 import freemarker.template.TemplateModelException;
57 import freemarker.template.TemplateModelRoot;
58 import freemarker.template.TemplateScalarModel;
59
60
61 public class FreemarkerGenerator implements Generator {
62   private Template template;
63
64   public FreemarkerGenerator(Template aTemplate) {
65     template = aTemplate;
66   }
67
68   public void generate(Object anOutputWriter, Map aValues, LoggerWrapper aLogger) throws GeneratorExc, GeneratorFailure {
69     if (!(anOutputWriter instanceof PrintWriter)) {
70       throw new GeneratorExc("Writer for a FreemarkerGenerator must be a PrintWriter");
71     }
72
73     try {
74       template.process((TemplateModelRoot) makeMapAdapter(aValues), (PrintWriter) anOutputWriter);
75     }
76     catch (Throwable t) {
77       aLogger.error("Exception occurred: "+t.getMessage(), t);
78
79       throw new GeneratorFailure(t);
80     }
81   }
82
83   private static TemplateScalarModel makeStringAdapter(String aString) {
84     return new SimpleScalar(aString);
85   }
86
87   private static TemplateHashModel makeMapAdapter(Map aMap)  {
88     return new MapAdapter(aMap);
89   }
90
91   private static TemplateListModel makeIteratorAdapter(Iterator anIterator) {
92     return new IteratorAdapter(anIterator);
93   }
94
95   private static TemplateMethodModel makeFunctionAdapter(Generator.Function aFunction) {
96     return new FunctionAdapter(aFunction);
97   }
98
99   private static TemplateHashModel makeBeanAdapter(Object anObject)  {
100     return new BeanAdapter(anObject);
101   }
102
103   public static TemplateModel makeAdapter(Object anObject) throws TemplateModelException {
104     if (anObject == null) {
105       return null;
106     }
107
108     if (anObject instanceof TemplateModel) {
109       return (TemplateModel) anObject;
110     }
111     else if (anObject instanceof Generator.Function) {
112       return makeFunctionAdapter((Generator.Function) anObject);
113     }
114     else if (anObject instanceof Integer) {
115       return makeStringAdapter(anObject.toString());
116     }
117     else if (anObject instanceof Boolean) {
118       if (((Boolean) anObject).booleanValue()) {
119         return makeStringAdapter("1");
120       }
121       else {
122         return makeStringAdapter("0");
123       }
124     }
125     else if (anObject instanceof String) {
126       return makeStringAdapter((String) anObject);
127     }
128     else if (anObject instanceof Map) {
129       return makeMapAdapter((Map) anObject);
130     }
131     else if (anObject instanceof Iterator) {
132       return makeIteratorAdapter((Iterator) anObject);
133     }
134     else if (anObject instanceof Collection) {
135       return makeIteratorAdapter(((Collection) anObject).iterator());
136     }
137     else if (anObject instanceof Number) {
138       return makeAdapter(new GeneratorFormatAdapters.NumberFormatAdapter((Number) anObject));
139     }
140     else if (anObject instanceof Date) {
141       return makeAdapter(new GeneratorFormatAdapters.DateFormatAdapter((Date) anObject));
142     }
143     else {
144       return makeBeanAdapter(anObject);
145     }
146   }
147
148   private static class MapAdapter implements TemplateModelRoot {
149     private Map map;
150     private Map valuesCache;
151
152     private MapAdapter(Map aMap) {
153       map = aMap;
154       valuesCache = new HashMap();
155     }
156
157     public void put(String aKey, TemplateModel aModel) {
158       valuesCache.put(aKey, aModel);
159     }
160
161     public void remove(String aKey) {
162     }
163
164     public boolean isEmpty() {
165       return map.isEmpty();
166     }
167
168     public TemplateModel get(String aKey) throws TemplateModelException {
169       try {
170         if (!valuesCache.containsKey(aKey)) {
171           Object value = map.get(aKey);
172
173           if (value == null && !map.containsKey(aKey)) {
174             throw new TemplateModelException("MapAdapter: no key "+aKey+" available");
175           }
176
177           valuesCache.put(aKey, makeAdapter(value));
178         }
179
180         return (TemplateModel) valuesCache.get(aKey);
181       }
182       catch (TemplateModelException e) {
183         throw e;
184       }
185       catch (Throwable t) {
186         throw new TemplateModelException(t.getMessage());
187       }
188     }
189   }
190
191   private static class IteratorAdapter implements TemplateListModel {
192     private Iterator iterator;
193     private List valuesCache;
194     private int position;
195
196     private IteratorAdapter(Iterator anIterator) {
197       iterator = anIterator;
198
199       valuesCache = new ArrayList();
200       position=0;
201
202
203       if (iterator instanceof RewindableIterator) {
204         ((RewindableIterator) iterator).rewind();
205       }
206     }
207
208     public boolean isEmpty() {
209       return valuesCache.isEmpty() && !iterator.hasNext();
210     }
211
212     private void getUntil(int anIndex) throws TemplateModelException {
213       while (valuesCache.size()<=anIndex && iterator.hasNext())
214       {
215         valuesCache.add(makeAdapter(iterator.next()));
216       }
217     }
218
219     public TemplateModel get(int anIndex) throws TemplateModelException {
220       getUntil(anIndex);
221
222       if (anIndex<valuesCache.size()) {
223         return (TemplateModel) valuesCache.get(anIndex);
224       }
225                         throw new TemplateModelException( "Iterator out of bounds" );
226     }
227
228     public boolean hasNext() {
229       return position<valuesCache.size() || iterator.hasNext();
230     }
231
232     public boolean isRewound() {
233       return position==0;
234     }
235
236     public TemplateModel next() throws TemplateModelException {
237       TemplateModel result;
238
239       if (hasNext()) {
240         result = get(position);
241         position++;
242       }
243       else
244         throw new TemplateModelException( "Iterator out of bounds" );
245
246       return result;
247     }
248
249     public void rewind() {
250       position=0;
251     }
252   }
253
254   private static class FunctionAdapter implements TemplateMethodModel {
255     private Generator.Function function;
256
257     FunctionAdapter(Generator.Function aFunction) {
258       function = aFunction;
259     }
260
261     public TemplateModel exec(List anArguments) throws TemplateModelException {
262       try {
263         return makeAdapter(function.perform(anArguments));
264       }
265       catch (Throwable t) {
266         throw new TemplateModelException(t.getMessage());
267       }
268     }
269
270     public boolean isEmpty() {
271       return false;
272     }
273
274   }
275
276   private static class BeanAdapter implements TemplateHashModel {
277     private Object object;
278
279     public BeanAdapter(Object anObject) {
280       object = anObject;
281     }
282
283     public void put(String aKey, TemplateModel aModel)  throws TemplateModelException  {
284       throw new TemplateModelException("FreemarkerGenerator$BeanAdapter.put not supported");
285     }
286
287     public void remove(String aKey) throws TemplateModelException  {
288       throw new TemplateModelException("FreemarkerGenerator$BeanAdapter.remove not supported");
289     }
290
291     public boolean isEmpty() {
292       return false;
293     }
294
295     public TemplateModel get(String aKey) throws TemplateModelException {
296       try {
297         if (PropertyUtils.isReadable(object, aKey)) {
298           return makeAdapter(PropertyUtils.getSimpleProperty(object, aKey));
299         }
300                                 return makeAdapter(MethodUtils.invokeExactMethod(object, "get", aKey));
301       }
302       catch (Throwable t) {
303         throw new TemplateModelException(t.getMessage());
304       }
305     }
306   }
307
308   public static class FreemarkerGeneratorLibrary implements Library {
309     private FileTemplateCache templateCache;
310
311     public FreemarkerGeneratorLibrary(File aTemplateRoot) {
312       templateCache = new FileTemplateCache(aTemplateRoot);
313       templateCache.setLoadingPolicy(FileTemplateCache.LOAD_ON_DEMAND);
314     }
315
316     public Generator makeGenerator(String anIdentifier) throws GeneratorExc, GeneratorFailure {
317       Template template = (Template) templateCache.getItem(anIdentifier, "template");
318
319       if (template==null) {
320         throw new GeneratorExc("FreemarkerGeneratorLibrary: Can't find template " +
321             templateCache.getDirectory() + File.separatorChar + anIdentifier);
322       }
323
324       return new FreemarkerGenerator(template);
325     }
326   }
327
328   public static class FreemarkerGeneratorLibraryFactory implements LibraryFactory {
329     private File basePath;
330
331     public FreemarkerGeneratorLibraryFactory(File aBasePath) {
332       basePath = aBasePath;
333     }
334
335     public Library makeLibrary(String anInitializationString) {
336       // todo: the initialization string should be parsed
337       return new FreemarkerGeneratorLibrary(new File(basePath, anInitializationString));
338     }
339   }
340 }