001 /* $Id: GStringTemplateEngine.java,v 1.10 2005/07/16 20:00:25 phk 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 ParameterArray(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 }