001 /* $Id: GStringTemplateEngine.java,v 1.9 2005/06/13 16:06:44 blackdrag Exp $
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.*;
048
049 import java.io.IOException;
050 import java.io.PrintWriter;
051 import java.io.Reader;
052 import java.io.StringWriter;
053 import java.io.Writer;
054 import java.security.AccessController;
055 import java.security.PrivilegedAction;
056 import java.util.Map;
057
058 import org.codehaus.groovy.control.CompilationFailedException;
059
060
061 /**
062 * @author tug@wilson.co.uk
063 *
064 */
065 public class GStringTemplateEngine extends TemplateEngine {
066 /* (non-Javadoc)
067 * @see groovy.text.TemplateEngine#createTemplate(java.io.Reader)
068 */
069 public Template createTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
070 return new GStringTemplate(reader);
071 }
072
073 private static class GStringTemplate implements Template {
074 final Closure template;
075
076 /**
077 * Turn the template into a writable Closure
078 * When executed the closure evaluates all the code embedded in the
079 * template and then writes a GString containing the fixed and variable items
080 * to the writer passed as a paramater
081 *
082 * For example:
083 *
084 * '<%= "test" %> of expr and <% test = 1 %>${test} script.'
085 *
086 * would compile into:
087 *
088 * { |out| out << "${"test"} of expr and "; test = 1 ; out << "${test} script."}.asWritable()
089 *
090 * @param reader
091 * @throws CompilationFailedException
092 * @throws ClassNotFoundException
093 * @throws IOException
094 */
095 public GStringTemplate(final Reader reader) throws CompilationFailedException, ClassNotFoundException, IOException {
096 final StringBuffer templateExpressions = new StringBuffer("package groovy.tmp.templates\n def getTemplate() { return { out -> out << \"\"\"");
097 boolean writingString = true;
098
099 while(true) {
100 int c = reader.read();
101
102 if (c == -1) break;
103
104 if (c == '<') {
105 c = reader.read();
106
107 if (c == '%') {
108 c = reader.read();
109
110 if (c == '=') {
111 parseExpression(reader, writingString, templateExpressions);
112 writingString = true;
113 continue;
114 } else {
115 parseSection(reader, writingString, templateExpressions);
116 writingString = false;
117 continue;
118 }
119 } else {
120 appendCharacter('<', templateExpressions, writingString);
121 writingString = true;
122 }
123 } else if (c == '"') {
124 appendCharacter('\\', templateExpressions, writingString);
125 writingString = true;
126 }
127
128 appendCharacter((char)c, templateExpressions, writingString);
129 writingString = true;
130 }
131
132 if (writingString) {
133 templateExpressions.append("\"\"\"");
134 }
135
136 templateExpressions.append("}.asWritable()}");
137
138 // System.out.println(templateExpressions.toString());
139
140 final ClassLoader parentLoader = getClass().getClassLoader();
141 final GroovyClassLoader loader =
142 (GroovyClassLoader) AccessController.doPrivileged(new PrivilegedAction() {
143 public Object run() {
144 return new GroovyClassLoader(parentLoader);
145 }
146 });
147 final Class groovyClass = loader.parseClass(new GroovyCodeSource(templateExpressions.toString(), "C", "x"));
148
149 try {
150 final GroovyObject object = (GroovyObject) groovyClass.newInstance();
151
152 this.template = (Closure)object.invokeMethod("getTemplate", null);
153 } catch (InstantiationException e) {
154 throw new ClassNotFoundException(e.getMessage());
155 } catch (IllegalAccessException e) {
156 throw new ClassNotFoundException(e.getMessage());
157 }
158 }
159
160 private static void appendCharacter(final char c,
161 final StringBuffer templateExpressions,
162 final boolean writingString)
163 {
164 if (!writingString) {
165 templateExpressions.append("out << \"\"\"");
166 }
167
168 templateExpressions.append(c);
169 }
170
171 /**
172 * Parse a <% .... %> section
173 * if we are writing a GString close and append ';'
174 * then write the section as a statement
175 *
176 * @param reader
177 * @param writingString
178 * @param templateExpressions
179 * @throws IOException
180 */
181 private static void parseSection(final Reader reader,
182 final boolean writingString,
183 final StringBuffer templateExpressions)
184 throws IOException
185 {
186 if (writingString) {
187 templateExpressions.append("\"\"\"; ");
188 }
189
190 while (true) {
191 int c = reader.read();
192
193 if (c == -1) break;
194
195 if (c =='%') {
196 c = reader.read();
197
198 if (c == '>') break;
199 }
200
201 templateExpressions.append((char)c);
202 }
203
204 templateExpressions.append("; ");
205 }
206
207 /**
208 * Parse a <%= .... %> expression
209 *
210 * @param reader
211 * @param writingString
212 * @param templateExpressions
213 * @throws IOException
214 */
215 private static void parseExpression(final Reader reader,
216 final boolean writingString,
217 final StringBuffer templateExpressions)
218 throws IOException
219 {
220 if (!writingString) {
221 templateExpressions.append("out << \"\"\"");
222 }
223
224 templateExpressions.append("${");
225
226 while (true) {
227 int c = reader.read();
228
229 if (c == -1) break;
230
231 if (c =='%') {
232 c = reader.read();
233
234 if (c == '>') break;
235 }
236
237 templateExpressions.append((char)c);
238 }
239
240 templateExpressions.append('}');
241 }
242
243 public Writable make() {
244 return make(null);
245 }
246
247 public Writable make(final Map map) {
248 final Closure delegatedClosure = (Closure)this.template.clone();
249
250 delegatedClosure.setDelegate(new Binding(map));
251
252 return new Writable() {
253 /* (non-Javadoc)
254 * @see groovy.lang.Writable#writeTo(java.io.Writer)
255 */
256 public Writer writeTo(final Writer writer) throws IOException {
257 delegatedClosure.call(new Object[] {new PrintWriter(writer)});
258
259 return writer;
260 }
261
262 /* (non-Javadoc)
263 * @see java.lang.Object#toString()
264 */
265 public String toString() {
266 final StringWriter stringWriter = new StringWriter();
267
268 try {
269 writeTo(stringWriter);
270
271 return stringWriter.toString();
272 } catch (final IOException e) {
273 return e.toString();
274 }
275 }
276
277 };
278 }
279 }
280 }