001    /*
002    @license.text@
003     */
004    package biz.hammurapi.eval;
005    
006    import java.io.FilterWriter;
007    import java.io.IOException;
008    import java.io.Reader;
009    import java.io.StringReader;
010    import java.io.StringWriter;
011    import java.io.Writer;
012    import java.util.Collection;
013    import java.util.HashSet;
014    import java.util.Iterator;
015    import java.util.Set;
016    
017    import biz.hammurapi.config.Context;
018    
019    /**
020     * @author Pavel Vlasov
021     * @version $Revision$
022     */
023    public class ExpandingFilterWriter extends FilterWriter {
024        private Context context;
025        private boolean stop;
026        
027        /**
028         * When pragma:stop token is encountered subsequent characters are not
029         * expanded until pragma:start token is encountered. pragma:start and pragma:stop are not output to 
030         * filtered writer.
031         */
032        public static final String PRAGMA_STOP = "pragma:stop";
033        public static final String PRAGMA_START = "pragma:start";
034    
035    //    private static final Logger logger = Logger.getLogger(ExpandingFilterWriter.class);
036    
037            private Set keySet;
038    
039        public ExpandingFilterWriter(Writer out, Context context) {
040            this(out, context, new HashSet());
041        }
042    
043        protected ExpandingFilterWriter(Writer out, Context context, Set keySet) {
044            super(out);
045            this.context = context;
046            this.keySet=keySet;
047        }
048    
049        private int lastChar;
050    
051        private StringBuffer keyBuffer;
052    
053        private static final int STATE_NORMAL = 0;
054    
055        private static final int STATE_DOLLAR = 1;
056    
057        private static final int STATE_EXPRESSION = 2;
058    
059        private static final int STATE_QUOTE = 3;
060    
061        private int state = STATE_NORMAL;
062    
063        public void write(int c) throws IOException {
064            switch (c) {
065                    case '$':
066                        switch (state) {
067                                case STATE_NORMAL:
068                                    state = STATE_DOLLAR;
069                                    break;
070                                case STATE_DOLLAR:
071                                    state = STATE_NORMAL;
072                                    out.write(c);
073                                    if (stop) {
074                                            out.write(c);
075                                    }
076    //                              out.write(b);
077                                    break;
078                                case STATE_EXPRESSION:
079                                case STATE_QUOTE:
080                                    toBuffer(c);
081                                    break;
082                                default:
083                                    throw new IOException("Invalid lexer state: " + state);
084                        }
085                        break;
086                    case '{':
087                        switch (state) {
088                                case STATE_EXPRESSION:
089                                case STATE_QUOTE:
090                                    toBuffer(c);
091                                    break;
092                                case STATE_NORMAL:
093                                    out.write(c);
094                                    break;
095                                case STATE_DOLLAR:
096                                    state = STATE_EXPRESSION;
097                                    break;
098                                default:
099                                    throw new IOException("Invalid lexer state: " + state);
100                        }
101                        break;
102                    case '}':
103                        switch (state) {
104                                case STATE_NORMAL:
105                                    out.write(c);
106                                    break;
107                                case STATE_DOLLAR:
108                                    state = STATE_NORMAL;
109                                    out.write(c);
110                                case STATE_EXPRESSION:
111                                    state = STATE_NORMAL;
112                                    if (keyBuffer == null || keyBuffer.length()==0) {
113                                        out.write('$');
114                                        out.write('{');
115                                        out.write('}');
116                                    } else if (PRAGMA_START.equals(keyBuffer.toString())){
117                                            stop=false;
118                                            keyBuffer=null;
119                                    } else if (PRAGMA_STOP.equals(keyBuffer.toString())){
120                                            stop=true;
121                                            keyBuffer=null;
122                                    } else if (stop) {
123                                    out.write("${");
124                                    out.write(keyBuffer.toString());
125                                    out.write("}");
126                                    keyBuffer=null;
127                                    } else {
128                                        String key = keyBuffer.toString();
129                                        keyBuffer = null;
130                                        
131                                    if (keySet.contains(key)) {
132                                            throw new CircularReferenceException("Circular reference: "+key);
133                                    }
134                                    
135                                    Object o=context.get(key);
136                                    if (o==null) {
137                                            out.write("${");
138                                            out.write(key);
139                                            out.write("}");
140                                    } else {                                
141                                            if (o instanceof Collection) {
142                                                    Iterator it = ((Collection) o).iterator();
143                                                    while (it.hasNext()) {
144                                                        Object object = it.next();
145                                                        if (object == null) {
146                                                            out.write("(null)");
147                                                        } else {
148                                                            try {
149                                                                    keySet.add(key);
150                                                                    out.write(toString(object, context, keySet));
151                                                            } finally {
152                                                                    keySet.remove(key);
153                                                            }
154                                                            
155                                                            if (it.hasNext()) {
156                                                                out.write(' ');
157                                                            }
158                                                        }
159                                                    }
160                                            } else {
161                                                    try {
162                                                            keySet.add(key);
163                                                            out.write(toString(o, context, keySet));
164                                                    } finally {
165                                                            keySet.remove(key);
166                                                    }
167                                            }
168                                            }
169                                    }
170                                    break;
171                                case STATE_QUOTE:
172                                    toBuffer(c);
173                                    break;
174                                default:
175                                    throw new IOException("Invalid lexer state: " + state);
176                            }
177                        break;
178                    case '"':
179                        switch (state) {
180                                case STATE_NORMAL:
181                                    out.write(c);
182                                    break;
183                                case STATE_DOLLAR:
184                                    state=STATE_NORMAL;
185                                    out.write('$');
186                                    out.write(c);
187                                    break;
188                                case STATE_EXPRESSION:
189                                    state=STATE_QUOTE;
190                                    toBuffer(c);
191                                    break;
192                                case STATE_QUOTE:
193                                    if (lastChar!='\\') {
194                                        state=STATE_EXPRESSION;
195                                    }
196                                toBuffer(c);
197                                break;
198                                default:
199                                    throw new IOException("Invalid lexer state: " + state);
200                        }
201                        break;
202                    default:
203                        switch (state) {
204                                case STATE_NORMAL:
205                                    out.write(c);
206                                    break;
207                                case STATE_DOLLAR:
208                                    state=STATE_NORMAL;
209                                    out.write('$');
210                                    out.write(c);
211                                    break;
212                                case STATE_EXPRESSION:
213                                case STATE_QUOTE:
214                                    toBuffer(c);
215                                    break;
216                                default:
217                                    throw new IOException("Invalid lexer state: " + state);
218                        }
219            }
220            lastChar = c;
221        }
222    
223        /**
224             * @param object
225             * @return
226             */
227            private static String toString(Object object, Context context, Set keySet) {
228                    try {
229                            StringWriter sw=new StringWriter();
230                    ExpandingFilterWriter efw=new ExpandingFilterWriter(sw, context, keySet);
231                    char[] buf=new char[1024];
232                    StringReader reader=new StringReader(object.toString());
233                    int l;
234                    while ((l=reader.read(buf))!=-1) {
235                            efw.write(buf, 0, l);
236                    }
237                    efw.close();
238                    sw.close();
239                    reader.close();
240                            return sw.toString();
241                    } catch (IOException e) {
242                            throw new EvaluationException("Shall never happen", e);
243                    }
244            }
245    
246            /**
247         * @param b
248         */
249        private void toBuffer(int b) {
250            if (keyBuffer == null) {
251                keyBuffer = new StringBuffer();
252            }
253            keyBuffer.append((char) b);
254        }
255    
256        public void close() throws IOException {
257            if (state == STATE_DOLLAR) {
258                out.write('$');
259            } else if (state == STATE_EXPRESSION /*|| state == STATE_QUOTE */) {
260                out.write('$');
261                out.write('{');
262            }
263    
264            if (keyBuffer != null) {
265                out.write(keyBuffer.toString());
266                keyBuffer = null;
267            }
268            out.close();
269            super.close();
270        }
271        
272        public void write(char[] cbuf, int off, int len) throws IOException {
273            for (int i=0; i<len; i++) {
274                write(cbuf[i+off]);
275            }
276        }
277        
278        public void write(String str, int off, int len) throws IOException {
279            for (int i=0; i<len; i++) {
280                write(str.charAt(i+off));
281            }
282        }
283        
284        public static Reader expand(Reader reader, Context context) throws IOException {
285                    return new StringReader(expandToString(reader, context));
286        }
287    
288            /**
289             * @param reader
290             * @param context
291             * @return
292             * @throws IOException
293             */
294            public static String expandToString(Reader reader, Context context) throws IOException {
295                    StringWriter sw=new StringWriter();
296            ExpandingFilterWriter efw=new ExpandingFilterWriter(sw, context);
297            char[] buf=new char[1024];
298            int l;
299            while ((l=reader.read(buf))!=-1) {
300                    efw.write(buf, 0, l);
301            }
302            efw.close();
303            sw.close();
304            reader.close();
305                    return sw.toString();
306            }
307    }