View Javadoc

1   /* $Id: GStringTemplateEngine.java,v 1.16 2005/10/20 09:21:56 tug Exp $
2   
3   Copyright 2004 (C) John Wilson. All Rights Reserved.
4   
5   Redistribution and use of this software and associated documentation
6   ("Software"), with or without modification, are permitted provided
7   that the following conditions are met:
8   
9   1. Redistributions of source code must retain copyright
10     statements and notices.  Redistributions must also contain a
11     copy of this document.
12  
13  2. Redistributions in binary form must reproduce the
14     above copyright notice, this list of conditions and the
15     following disclaimer in the documentation and/or other
16     materials provided with the distribution.
17  
18  3. The name "groovy" must not be used to endorse or promote
19     products derived from this Software without prior written
20     permission of The Codehaus.  For written permission,
21     please contact info@codehaus.org.
22  
23  4. Products derived from this Software may not be called "groovy"
24     nor may "groovy" appear in their names without prior written
25     permission of The Codehaus. "groovy" is a registered
26     trademark of The Codehaus.
27  
28  5. Due credit should be given to The Codehaus -
29     http://groovy.codehaus.org/
30  
31  THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
32  ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
33  NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
34  FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL
35  THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
36  INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
37  (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
38  SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
39  HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
40  STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
41  ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
42  OF THE POSSIBILITY OF SUCH DAMAGE.
43  
44  */
45  package groovy.text;
46  
47  import groovy.lang.Closure;
48  import groovy.lang.GroovyClassLoader;
49  import groovy.lang.GroovyCodeSource;
50  import groovy.lang.GroovyObject;
51  import groovy.lang.Writable;
52  
53  import java.io.IOException;
54  import java.io.Reader;
55  import java.security.AccessController;
56  import java.security.PrivilegedAction;
57  import java.util.Map;
58  
59  import org.codehaus.groovy.control.CompilationFailedException;
60  
61  
62  /***
63  * @author tug@wilson.co.uk
64  *
65  */
66  public class GStringTemplateEngine extends TemplateEngine {
67      /* (non-Javadoc)
68       * @see groovy.text.TemplateEngine#createTemplate(java.io.Reader)
69       */
70      public Template createTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
71          return new GStringTemplate(reader);
72      }
73  
74      private static class GStringTemplate implements Template {
75          final Closure template;
76  
77          /***
78           * Turn the template into a writable Closure
79           * When executed the closure evaluates all the code embedded in the
80           * template and then writes a GString containing the fixed and variable items
81           * to the writer passed as a paramater
82           *
83           * For example:
84           *
85           * '<%= "test" %> of expr and <% test = 1 %>${test} script.'
86           *
87           * would compile into:
88           *
89           * { |out| out << "${"test"} of expr and "; test = 1 ; out << "${test} script."}.asWritable()
90           *
91           * @param reader
92           * @throws CompilationFailedException
93           * @throws ClassNotFoundException
94           * @throws IOException
95           */
96          public GStringTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
97          final StringBuffer templateExpressions = new StringBuffer("package groovy.tmp.templates\n def getTemplate() { return { out -> delegate = new Binding(delegate); out << \"\"\"");
98          boolean writingString = true;
99         
100             while(true) {
101                 int c = reader.read();
102 
103                     if (c == -1) break;
104 
105                 if (c == '<') {
106                     c = reader.read();
107 
108                     if (c == '%') {
109                         c = reader.read();
110 
111                         if (c == '=') {
112                                 parseExpression(reader, writingString, templateExpressions);
113                                 writingString = true;
114                                 continue;
115                         } else {
116                                 parseSection(c, reader, writingString, templateExpressions);
117                                 writingString = false;
118                                 continue;
119                         }
120                     } else {
121                         appendCharacter('<', templateExpressions, writingString);
122                         writingString = true;
123                     }
124                 } else if (c == '"') {
125                         appendCharacter('//', templateExpressions, writingString);
126                         writingString = true;
127                    }
128 
129                     appendCharacter((char)c, templateExpressions, writingString);
130                     writingString = true;
131             }
132 
133             if (writingString) {
134                     templateExpressions.append("\"\"\"");
135             }
136 
137             templateExpressions.append("}.asWritable()}");
138 
139 //            System.out.println(templateExpressions.toString());
140 
141             final ClassLoader parentLoader = getClass().getClassLoader();
142             final GroovyClassLoader loader =
143                 (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
144                     public Object run() {
145                         return new GroovyClassLoader(parentLoader);
146                     }
147                 });
148             final Class groovyClass = loader.parseClass(new GroovyCodeSource(templateExpressions.toString(), "C", "x"));
149 
150             try {
151                 final GroovyObject object = (GroovyObject) groovyClass.newInstance();
152 
153                 this.template = (Closure)object.invokeMethod("getTemplate", null);
154             } catch (InstantiationException e) {
155                 throw new ClassNotFoundException(e.getMessage());
156             } catch (IllegalAccessException e) {
157                 throw new ClassNotFoundException(e.getMessage());
158             }
159         }
160 
161         private static void appendCharacter(final char c,
162                                             final StringBuffer templateExpressions,
163                                             final boolean writingString)
164         {
165             if (!writingString) {
166                 templateExpressions.append("out << \"\"\"");
167             }
168 
169             templateExpressions.append(c);
170         }
171 
172         /***
173          * Parse a <% .... %> section
174          * if we are writing a GString close and append ';'
175          * then write the section as a statement
176          *
177          * @param pendingC
178          * @param reader
179          * @param writingString
180          * @param templateExpressions
181          * @throws IOException
182          */
183         private static void parseSection(final int pendingC,
184                                          final Reader reader,
185                                          final boolean writingString,
186                                          final StringBuffer templateExpressions)
187             throws IOException
188         {
189             if (writingString) {
190                 templateExpressions.append("\"\"\"; ");
191             }
192             templateExpressions.append((char)pendingC);
193 
194                 while (true) {
195                     int c = reader.read();
196 
197                     if (c == -1) break;
198 
199                     if (c =='%') {
200                         c = reader.read();
201 
202                         if (c == '>') break;
203                         
204                         templateExpressions.append('%');
205                     }
206 
207                     templateExpressions.append((char)c);
208                 }
209 
210                 templateExpressions.append("; ");
211         }
212 
213         /***
214          * Parse a <%= .... %> expression
215          *
216          * @param reader
217          * @param writingString
218          * @param templateExpressions
219          * @throws IOException
220          */
221         private static void parseExpression(final Reader reader,
222                                           final boolean writingString,
223                                           final StringBuffer templateExpressions)
224             throws IOException
225         {
226             if (!writingString) {
227                 templateExpressions.append("out << \"\"\"");
228             }
229 
230             templateExpressions.append("${");
231 
232                 while (true) {
233                     int c = reader.read();
234 
235                     if (c == -1) break;
236 
237                     if (c =='%') {
238                         c = reader.read();
239 
240                         if (c == '>') break;
241                         
242                         templateExpressions.append('%');
243                     }
244 
245                     templateExpressions.append((char)c);
246                 }
247 
248             templateExpressions.append('}');
249         }
250 
251         public Writable make() {
252            return make(null);
253        }
254 
255        public Writable make(final Map map) {
256        final Closure template = (Closure)this.template.clone();
257            
258            template.setDelegate(map);
259            
260            return (Writable)template;
261        }
262     }
263 }