001    /* $Id: GStringTemplateEngine.java,v 1.16 2005/10/20 09:21:56 tug 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.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("; ");
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    }