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