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    }