001 /* $Id: GStringTemplateEngine.java 3386 2006-01-12 10:00:23Z tug $
002
003 Copyright 2004 (C) John Wilson. All Rights Reserved.
004
005 Redistribution and use of this software and associated documentation
006 ("Software"), with or without modification, are permitted provided
007 that the following conditions are met:
008
009 1. Redistributions of source code must retain copyright
010 statements and notices. Redistributions must also contain a
011 copy of this document.
012
013 2. Redistributions in binary form must reproduce the
014 above copyright notice, this list of conditions and the
015 following disclaimer in the documentation and/or other
016 materials provided with the distribution.
017
018 3. The name "groovy" must not be used to endorse or promote
019 products derived from this Software without prior written
020 permission of The Codehaus. For written permission,
021 please contact info@codehaus.org.
022
023 4. Products derived from this Software may not be called "groovy"
024 nor may "groovy" appear in their names without prior written
025 permission of The Codehaus. "groovy" is a registered
026 trademark of The Codehaus.
027
028 5. Due credit should be given to The Codehaus -
029 http://groovy.codehaus.org/
030
031 THIS SOFTWARE IS PROVIDED BY THE CODEHAUS AND CONTRIBUTORS
032 ``AS IS'' AND ANY EXPRESSED OR IMPLIED WARRANTIES, INCLUDING, BUT
033 NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND
034 FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL
035 THE CODEHAUS OR ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
036 INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
037 (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
038 SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
039 HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
040 STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
041 ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED
042 OF THE POSSIBILITY OF SUCH DAMAGE.
043
044 */
045 package groovy.text;
046
047 import groovy.lang.Closure;
048 import groovy.lang.GroovyClassLoader;
049 import groovy.lang.GroovyCodeSource;
050 import groovy.lang.GroovyObject;
051 import groovy.lang.Writable;
052
053 import java.io.IOException;
054 import java.io.Reader;
055 import java.security.AccessController;
056 import java.security.PrivilegedAction;
057 import java.util.Map;
058
059 import org.codehaus.groovy.control.CompilationFailedException;
060
061
062 /**
063 * @author tug@wilson.co.uk
064 *
065 */
066 public class GStringTemplateEngine extends TemplateEngine {
067 /* (non-Javadoc)
068 * @see groovy.text.TemplateEngine#createTemplate(java.io.Reader)
069 */
070 public Template createTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
071 return new GStringTemplate(reader);
072 }
073
074 private static class GStringTemplate implements Template {
075 final Closure template;
076
077 /**
078 * Turn the template into a writable Closure
079 * When executed the closure evaluates all the code embedded in the
080 * template and then writes a GString containing the fixed and variable items
081 * to the writer passed as a paramater
082 *
083 * For example:
084 *
085 * '<%= "test" %> of expr and <% test = 1 %>${test} script.'
086 *
087 * would compile into:
088 *
089 * { |out| out << "${"test"} of expr and "; test = 1 ; out << "${test} script."}.asWritable()
090 *
091 * @param reader
092 * @throws CompilationFailedException
093 * @throws ClassNotFoundException
094 * @throws IOException
095 */
096 public GStringTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
097 final StringBuffer templateExpressions = new StringBuffer("package groovy.tmp.templates\n def getTemplate() { return { out -> delegate = new Binding(delegate); out << \"\"\"");
098 boolean writingString = true;
099
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(";\n ");
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 }