KickJava   Java API By Example, From Geeks To Geeks.

Java > Open Source Codes > com > sslexplorer > boot > ReplacementEngine


1 /*
2  * SSL-Explorer
3  *
4  * Copyright (C) 2003-2006 3SP LTD. All Rights Reserved
5  *
6  * This program is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; either version 2 of
9  * the License, or (at your option) any later version.
10  * This program is distributed in the hope that it will be useful,
11  * but WITHOUT ANY WARRANTY; without even the implied warranty of
12  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13  * GNU General Public License for more details.
14  *
15  * You should have received a copy of the GNU General Public
16  * License along with this program; if not, write to the Free Software
17  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
18  */

19             
20 package com.sslexplorer.boot;
21
22 import java.io.IOException JavaDoc;
23 import java.io.InputStream JavaDoc;
24 import java.io.OutputStream JavaDoc;
25 import java.util.ArrayList JavaDoc;
26 import java.util.HashMap JavaDoc;
27 import java.util.Iterator JavaDoc;
28 import java.util.List JavaDoc;
29 import java.util.regex.Matcher JavaDoc;
30 import java.util.regex.Pattern JavaDoc;
31
32 import org.apache.commons.logging.Log;
33 import org.apache.commons.logging.LogFactory;
34
35 /**
36  * <p>
37  * A helper that provides facilities for a list of regular expressions to be
38  * built up and the performed on some content in one go, with the replacement
39  * value being provided by a callback method.
40  * <p>
41  * This class represents the notion of a replacement engine. The user of this
42  * object creates an instance of a replacement engine then adds all of the
43  * patterns that they wish to be search for (using
44  * {@link #addPattern(String, Replacer, String)}.
45  * <p>
46  * This method also expects a {@link com.sslexplorer.boot.Replacer}
47  * implementation to be provided. When the engine is instructed to process some
48  * content, every time a match is found the appropriate
49  * {@link com.sslexplorer.boot.Replacer#getReplacement(Pattern, Matcher, String)}
50  * method will be called. The value that is returned from this method will then
51  * replace the matched string.
52  * <p>
53  * The two primary uses for this class can be found in the <i>Replacement proxy</i>
54  * feature and the <i>Appplication Extension</i>. Replacement proxy uses it to
55  * replace hyperlinks with HTML content with proxied links and the Application
56  * Extension uses it to dynamically replace provided application arguments with
57  * pre-defined strings.
58  * <p>
59  * As processing regular expressions can be a fairly intensive task , a cached
60  * pool of compile regular expression is also maintained as they may be re-used.
61  *
62  * @author Brett Smith <a HREF="mailto: brett@3sp.com">&lt;brett@3sp.com&gt;</a>
63  */

64
65 public class ReplacementEngine {
66     
67     final static Log log = LogFactory.getLog(ReplacementEngine.class);
68
69     // Private instance variables
70
private final StringBuffer JavaDoc inputBuffer = new StringBuffer JavaDoc();
71     private final StringBuffer JavaDoc workBuffer = new StringBuffer JavaDoc();
72     private List JavaDoc replacementsList = new ArrayList JavaDoc();
73     private boolean caseSensitive = true;
74     private boolean dotAll = false;
75     private static PatternPool patternPool;
76     private String JavaDoc charset;
77     private Encoder encoder;
78
79     /**
80      * Constructor
81      */

82     public ReplacementEngine() {
83         patternPool = new PatternPool();
84     }
85     
86     /**
87      * Set the {@link Encoder} implementation to use. This is a callback
88      * interface that has the oppurtunity to do post processing on any
89      * replaced values.
90      *
91      * @param encoder
92      */

93     public void setEncoder(Encoder encoder) {
94         this.encoder = encoder;
95     }
96
97     /**
98      * Set the character encoding of the content to replace
99      *
100      * @param charset character encoding
101      */

102     public void setEncoding(String JavaDoc charset) {
103         this.charset = charset;
104     }
105
106     /**
107      * Get the instance of the pattern pool.
108      *
109      * @return instance of pattern pool
110      */

111     public static PatternPool getPatternPool() {
112         if (patternPool == null) {
113             patternPool = new PatternPool();
114         }
115         return patternPool;
116     }
117
118     /**
119      * Set whether every match on every record should be processed
120      *
121      * @param dotAll process every match on every record
122      */

123     public void setDotAll(boolean dotAll) {
124         this.dotAll = dotAll;
125     }
126
127     /**
128      * Set whether the matching is case sensitive
129      *
130      * @param caseSensitive is case sensitve
131      */

132     public void setCaseSensitive(boolean caseSensitive) {
133         this.caseSensitive = caseSensitive;
134     }
135
136     /**
137      * Add a new pattern to the replacement engine.
138      *
139      * @param pattern patter to search for
140      * @param replacer replace containing callback to get replacement value
141      * @param replacementPattern optional replacement pattern (replacer
142      * implementation may require it)
143      */

144     public synchronized void addPattern(String JavaDoc pattern, Replacer replacer, String JavaDoc replacementPattern) {
145         // Pattern p = Pattern.compile(pattern, ( caseSensitive ? 0 :
146
// Pattern.CASE_INSENSITIVE ) + ( dotAll ? Pattern.DOTALL : 0 ) );
147
replacementsList.add(new ReplaceOp(replacer, pattern, replacementPattern));
148     }
149
150     /**
151      * Replace all occurences of all registered pattern in the provided string
152      * and return the result as a second string
153      *
154      * @param input input string
155      * @return processed string
156      */

157     public synchronized String JavaDoc replace(String JavaDoc input) {
158         Iterator JavaDoc it = replacementsList.iterator();
159
160         inputBuffer.setLength(0);
161         inputBuffer.append(input);
162
163         workBuffer.setLength(0);
164         workBuffer.ensureCapacity(input.length());
165         
166         if (log.isDebugEnabled())
167             log.debug("Starting replacement on string on " + input.length() + " characters");
168         
169         while (it.hasNext()) {
170             ReplaceOp op = (ReplaceOp) it.next();
171             
172             if (log.isDebugEnabled())
173                 log.debug("Replacemnt " + op.replacePattern + " [" + op.pattern + "]");
174             
175             Pattern JavaDoc p = getPatternPool().getPattern(op.pattern, caseSensitive, dotAll);
176             
177             if (log.isDebugEnabled())
178                 log.debug("Got pattern from pool");
179             
180             try {
181                 replaceInto(p, op.replacePattern, op.replacer, inputBuffer, workBuffer);
182                 if (log.isDebugEnabled())
183                     log.debug("Replacement complete");
184             } catch (Throwable JavaDoc t) {
185                 if (log.isDebugEnabled())
186                     log.debug("Error replacing.", t);
187             } finally {
188                 if (log.isDebugEnabled())
189                     log.debug("Releasing pattern from pool.");
190                 patternPool.releasePattern(p);
191             }
192             inputBuffer.setLength(0);
193             inputBuffer.append(workBuffer);
194         }
195         if (log.isDebugEnabled())
196             log.debug("Finished replacing. Returning string of " +inputBuffer.length() + "characters");
197         return (inputBuffer.toString());
198     }
199
200     /**
201      * Replace all occurences of all registered patterns in all data read from
202      * the input stream and write the processed content back to the provided
203      * output stream.
204      * <p>
205      * <b>Note, this method current reads the entire stream into memory as a
206      * string before performing the replacements. Beware of this when are using
207      * this method. A more efficient method may come later.</b>
208      *
209      * @param in input stream
210      * @param out output stream
211      * @return bytes ready
212      * @throws IOException on any IO error
213      */

214     public long replace(InputStream JavaDoc in, OutputStream JavaDoc out) throws IOException JavaDoc {
215         if (log.isDebugEnabled())
216             log.debug("Replacing using streams, reading stream into memory");
217         StringBuffer JavaDoc str = new StringBuffer JavaDoc(4096);
218         byte[] buf = new byte[32768];
219         int read;
220         while ((read = in.read(buf)) > -1) {
221             str.append(charset == null ? new String JavaDoc(buf, 0, read) : new String JavaDoc(buf, 0, read, charset));
222             if (log.isDebugEnabled())
223                 log.debug("Got block of " + read + ", waiting for next one");
224         }
225         if (log.isDebugEnabled())
226             log.debug("Read all blocks, performing replacement");
227         byte[] b = charset == null ? replace(str.toString()).getBytes() : replace(str.toString()).getBytes(charset);
228         if (log.isDebugEnabled())
229             log.debug("Writing replaced content back (" + b.length + " bytes)");
230         out.write(b);
231         return b.length;
232     }
233
234     // Supporting methods
235

236     private void replaceInto(Pattern JavaDoc pattern, String JavaDoc replacementPattern, Replacer replacer, StringBuffer JavaDoc input, StringBuffer JavaDoc work) {
237         work.ensureCapacity(input.length());
238         work.setLength(0);
239         if (log.isDebugEnabled())
240             log.debug("Getting matcher");
241         Matcher JavaDoc m = pattern.matcher(input);
242         log.debug("Got matcher, finding first occurence.");
243         while (m.find()) {
244             if (log.isDebugEnabled())
245                 log.debug("Found occurence");
246             String JavaDoc repl = replacer.getReplacement(pattern, m, replacementPattern);
247             if (repl != null) {
248                 if (log.isDebugEnabled())
249                     log.debug("Found replacement, appending");
250                 if(encoder == null) {
251                     m.appendReplacement(work, Util.escapeForRegexpReplacement(repl));
252                 }
253                 else {
254                     m.appendReplacement(work, encoder.encode(Util.escapeForRegexpReplacement(repl)));
255                 }
256             }
257         }
258         if (log.isDebugEnabled())
259             log.debug("Processed matches, appending replacement.");
260         m.appendTail(work);
261     }
262
263     // Supporting classes
264

265     class ReplaceOp {
266         String JavaDoc pattern;
267         Replacer replacer;
268         String JavaDoc replacePattern;
269
270         ReplaceOp(Replacer replacer, String JavaDoc pattern, String JavaDoc replacePattern) {
271             this.replacer = replacer;
272             this.pattern = pattern;
273             this.replacePattern = replacePattern;
274         }
275     }
276
277     /**
278      * A cached pool of compiled regular expressions.
279      *
280      * @author Brett Smith <a HREF="mailto: brett@3sp.com">&lt;brett@3sp.com&gt;</a>
281      */

282     public static class PatternPool {
283         private HashMap JavaDoc patterns;
284         private HashMap JavaDoc locks;
285
286         PatternPool() {
287             patterns = new HashMap JavaDoc();
288             locks = new HashMap JavaDoc();
289         }
290
291         /**
292          * Get a compiled {@link Pattern} given the regular expression as text,
293          * whether the match should be case sensitive and whether all matches on
294          * every record (line) should be processed.
295          * <p>
296          * When a pattern is first requested a pool of 10 instances are created.
297          * Sub-sequent requests for the same pattern will then return one of
298          * these instances. The pattern will then be locked until
299          * {@link #releasePattern(Pattern)} is called. If there are no unlocked
300          * patterns in the pool, the caller will be blocked until one becomes
301          * available.
302          *
303          * @param pattern pattern
304          * @param caseSensitive case sensitive match
305          * @param dotAll match all matches on a single record
306          * @return compiled patter
307          */

308         public Pattern JavaDoc getPattern(String JavaDoc pattern, boolean caseSensitive, boolean dotAll) {
309             String JavaDoc cacheKey = pattern + "_" + caseSensitive + "_" + dotAll;
310             List JavaDoc pool = null;
311             synchronized (patterns) {
312                 pool = (List JavaDoc) patterns.get(cacheKey);
313                 if (pool == null) {
314                     pool = new ArrayList JavaDoc();
315                     patterns.put(cacheKey, pool);
316                 }
317             }
318             synchronized (pool) {
319                 while (true) {
320                     if (pool.size() < 10) {
321                         Pattern JavaDoc p = Pattern.compile(pattern, (!caseSensitive ? Pattern.CASE_INSENSITIVE : 0)
322                                         + (dotAll ? Pattern.DOTALL : 0));
323                         pool.add(p);
324                         locks.put(p, p);
325                         if (log.isDebugEnabled())
326                             log.debug("Created new pattern and locked");
327                         return p;
328                     } else {
329                         for (Iterator JavaDoc i = pool.listIterator(); i.hasNext();) {
330                             Pattern JavaDoc p = (Pattern JavaDoc) i.next();
331                             if (!locks.containsKey(p)) {
332                                 if (log.isDebugEnabled())
333                                     log.debug("Found a free pattern");
334                                 locks.put(p, p);
335                                 return p;
336                             }
337                         }
338                         synchronized (locks) {
339                             try {
340                                 if (log.isDebugEnabled())
341                                     log.debug("No free patterns, waiting for one to become available");
342                                 locks.wait();
343                             } catch (Exception JavaDoc e) {
344                             }
345                         }
346                     }
347                 }
348             }
349         }
350
351         /**
352          * Release a patterns lock.
353          *
354          * @param pattern pattern to release
355          * @see #getPattern(String, boolean, boolean)
356          */

357         public void releasePattern(Pattern JavaDoc pattern) {
358             synchronized (locks) {
359                 locks.remove(pattern);
360                 locks.notifyAll();
361             }
362         }
363     }
364     
365     public interface Encoder {
366         public String JavaDoc encode(String JavaDoc decoded);
367     }
368
369 }
Popular Tags