001/**
002 * Copyright (c) 2001, Sergey A. Samokhodkin
003 * All rights reserved.
004 * <p>
005 * Redistribution and use in source and binary forms, with or without modification,
006 * are permitted provided that the following conditions are met:
007 * <p>
008 * - Redistributions of source code must retain the above copyright notice,
009 * this list of conditions and the following disclaimer.
010 * - Redistributions in binary form
011 * must reproduce the above copyright notice, this list of conditions and the following
012 * disclaimer in the documentation and/or other materials provided with the distribution.
013 * - Neither the name of jregex nor the names of its contributors may be used
014 * to endorse or promote products derived from this software without specific prior
015 * written permission.
016 * <p>
017 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY
018 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
019 * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
020 * IN NO EVENT SHALL THE REGENTS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT,
021 * INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
022 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS;
023 * OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
024 * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY
025 * WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
026 *
027 * @version 1.2_01
028 */
029
030package regexodus;
031
032import java.io.IOException;
033import java.io.Reader;
034import java.io.Serializable;
035import java.io.Writer;
036
037/**
038 * <b>The Replacer class</b> suggests some methods to replace occurrences of a pattern
039 * either by a result of evaluation of a perl-like expression, or by a plain string,
040 * or according to a custom substitution model, provided as a Substitution interface implementation.<br>
041 * A Replacer instance may be obtained either using Pattern.replacer(...) method, or by constructor:<pre>
042 * Pattern p=new Pattern("\\w+");
043 * Replacer perlExpressionReplacer=p.replacer("[$&amp;]");
044 * //or another way to do the same
045 * Substitution myOwnModel=new Substitution(){
046 *    public void appendSubstitution(MatchResult match,TextBuffer tb){
047 *       tb.append('[');
048 *       match.getGroup(MatchResult.MATCH,tb);
049 *       tb.append(']');
050 *    }
051 * }
052 * Replacer myVeryOwnReplacer=new Replacer(p,myOwnModel);
053 * </pre>
054 * The second method is much more verbose, but gives more freedom.
055 * To perform a replacement call replace(someInput):<pre>
056 * System.out.print(perlExpressionReplacer.replace("All your base "));
057 * System.out.println(myVeryOwnReplacer.replace("are belong to us"));
058 * //result: "[All] [your] [base] [are] [belong] [to] [us]"
059 * </pre>
060 * This code was mostly written in 2001, I hope the reference isn't too outdated...
061 * @see Substitution
062 * @see PerlSubstitution
063 * @see Replacer#Replacer(regexodus.Pattern, regexodus.Substitution)
064 */
065
066public class Replacer implements Serializable {
067    private static final long serialVersionUID = 2528136757932720807L;
068
069    private Pattern pattern;
070    private Substitution substitution;
071
072    /**
073     * Unlikely to be used directly.
074     * @param pattern a regexodus.Pattern that determines what should be replaced
075     * @param substitution an implementation of the Substitution interface, which allows custom replacement behavior
076     */
077    public Replacer(Pattern pattern, Substitution substitution) {
078        this.pattern = pattern;
079        this.substitution = substitution;
080    }
081
082    /**
083     * Constructs a Replacer from a Pattern and a String to replace occurrences of the Pattern with.
084     * @param pattern a regexodus.Pattern that determines what should be replaced
085     * @param substitution a String that will be used to replace occurrences of the Pattern
086     */
087    public Replacer(Pattern pattern, String substitution) {
088        this(pattern, substitution, true);
089    }
090
091    public Replacer(Pattern pattern, String substitution, boolean isPerlExpr) {
092        this.pattern = pattern;
093        this.substitution = isPerlExpr ? new PerlSubstitution(substitution) :
094                new DummySubstitution(substitution);
095    }
096
097    public void setSubstitution(String s, boolean isPerlExpr) {
098        substitution = isPerlExpr ? new PerlSubstitution(s) :
099                new DummySubstitution(s);
100    }
101
102    /**
103     * Takes all instances in text of the Pattern this was constructed with, and replaces them with substitution.
104     * @param text a String, StringBuilder, or other CharSequence that may contain the text to replace
105     * @return the post-replacement text
106     */
107    public String replace(CharSequence text) {
108        TextBuffer tb = wrap(new StringBuilder(text.length()));
109        replace(pattern.matcher(text), substitution, tb);
110        return tb.toString();
111    }
112
113    public String replace(char[] chars, int off, int len) {
114        TextBuffer tb = wrap(new StringBuilder(len));
115        replace(pattern.matcher(chars, off, len), substitution, tb);
116        return tb.toString();
117    }
118
119    public String replace(MatchResult res, int group) {
120        TextBuffer tb = wrap(new StringBuilder());
121        replace(pattern.matcher(res, group), substitution, tb);
122        return tb.toString();
123    }
124
125    @GwtIncompatible
126    public String replace(Reader text, int length) throws IOException {
127        TextBuffer tb = wrap(new StringBuilder(length >= 0 ? length : 0));
128        replace(pattern.matcher(text, length), substitution, tb);
129        return tb.toString();
130    }
131
132    /**
133     * Takes all occurrences of the pattern this was constructed with in text and replaces them with the substitution.
134     * Appends the replaced text into sb.
135     * @param text a String, StringBuilder, or other CharSequence that may contain the text to replace
136     * @param sb the StringBuilder to append the result into
137     * @return the number of individual replacements performed; the results are applied to sb
138     */
139    public int replace(CharSequence text, StringBuilder sb) {
140        return replace(pattern.matcher(text), substitution, wrap(sb));
141    }
142
143    /**
144     */
145    public int replace(char[] chars, int off, int len, StringBuilder sb) {
146        return replace(chars, off, len, wrap(sb));
147    }
148
149    /**
150     */
151    public int replace(MatchResult res, int group, StringBuilder sb) {
152        return replace(res, group, wrap(sb));
153    }
154
155    /**
156     */
157    public int replace(MatchResult res, String groupName, StringBuilder sb) {
158        return replace(res, groupName, wrap(sb));
159    }
160
161    @GwtIncompatible
162    public int replace(Reader text, int length, StringBuilder sb) throws IOException {
163        return replace(text, length, wrap(sb));
164    }
165
166    /**
167     */
168    public int replace(CharSequence text, TextBuffer dest) {
169        return replace(pattern.matcher(text), substitution, dest);
170    }
171
172    /**
173     */
174    private int replace(char[] chars, int off, int len, TextBuffer dest) {
175        return replace(pattern.matcher(chars, off, len), substitution, dest);
176    }
177
178    /**
179     */
180    private int replace(MatchResult res, int group, TextBuffer dest) {
181        return replace(pattern.matcher(res, group), substitution, dest);
182    }
183
184    /**
185     */
186    private int replace(MatchResult res, String groupName, TextBuffer dest) {
187        return replace(pattern.matcher(res, groupName), substitution, dest);
188    }
189
190    @GwtIncompatible
191    private int replace(Reader text, int length, TextBuffer dest) throws IOException {
192        return replace(pattern.matcher(text, length), substitution, dest);
193    }
194
195    /**
196     * Replaces all occurrences of a matcher's pattern in a matcher's target
197     * by a given substitution appending the result to a buffer.<br>
198     * The substitution starts from current matcher's position, current match
199     * not included.
200     */
201    public static int replace(Matcher m, Substitution substitution, TextBuffer dest) {
202        boolean firstPass = true;
203        int c = 0;
204        while (m.find()) {
205            if (m.end() == 0 && !firstPass) continue;  //allow to replace at "^"
206            if (m.start() > 0) m.getGroup(MatchResult.PREFIX, dest);
207            substitution.appendSubstitution(m, dest);
208            c++;
209            m.setTarget(m, MatchResult.SUFFIX);
210            firstPass = false;
211        }
212        m.getGroup(MatchResult.TARGET, dest);
213        return c;
214    }
215
216    /**
217     * Replaces the first n occurrences of a matcher's pattern, where n is equal to count,
218     * in a matcher's target by a given substitution, appending the result to a buffer.
219     * <br>
220     * The substitution starts from current matcher's position, current match not included.
221     */
222    public static int replace(Matcher m, Substitution substitution, TextBuffer dest, int count) {
223        boolean firstPass = true;
224        int c = 0;
225        while (c < count && m.find()) {
226            if (m.end() == 0 && !firstPass) continue;  //allow to replace at "^"
227            if (m.start() > 0) m.getGroup(MatchResult.PREFIX, dest);
228            substitution.appendSubstitution(m, dest);
229            c++;
230            m.setTarget(m, MatchResult.SUFFIX);
231            firstPass = false;
232        }
233        m.getGroup(MatchResult.TARGET, dest);
234        return c;
235    }
236
237    @GwtIncompatible
238    private static int replace(Matcher m, Substitution substitution, Writer out) throws IOException {
239        try {
240            return replace(m, substitution, wrap(out));
241        } catch (WriteException e) {
242            throw e.reason;
243        }
244    }
245
246    @GwtIncompatible
247    public void replace(CharSequence text, Writer out) throws IOException {
248        replace(pattern.matcher(text), substitution, out);
249    }
250
251    @GwtIncompatible
252    public void replace(char[] chars, int off, int len, Writer out) throws IOException {
253        replace(pattern.matcher(chars, off, len), substitution, out);
254    }
255
256    @GwtIncompatible
257    public void replace(MatchResult res, int group, Writer out) throws IOException {
258        replace(pattern.matcher(res, group), substitution, out);
259    }
260
261    @GwtIncompatible
262    public void replace(MatchResult res, String groupName, Writer out) throws IOException {
263        replace(pattern.matcher(res, groupName), substitution, out);
264    }
265
266    @GwtIncompatible
267    public void replace(Reader in, int length, Writer out) throws IOException {
268        replace(pattern.matcher(in, length), substitution, out);
269    }
270
271    private static class DummySubstitution implements Substitution {
272        String str;
273
274        DummySubstitution(String s) {
275            str = s;
276        }
277
278        public void appendSubstitution(MatchResult match, TextBuffer res) {
279            if (str != null) res.append(str);
280        }
281    }
282
283    public static TextBuffer wrap(final StringBuilder sb) {
284        return new TextBuffer() {
285            public void append(char c) {
286                sb.append(c);
287            }
288
289            public void append(char[] chars, int start, int len) {
290                sb.append(chars, start, len);
291            }
292
293            public void append(String s) {
294                sb.append(s);
295            }
296
297            public String toString() {
298                return sb.toString();
299            }
300        };
301    }
302
303    @GwtIncompatible
304    private static TextBuffer wrap(final Writer writer) {
305        return new TextBuffer() {
306            public void append(char c) {
307                try {
308                    writer.write(c);
309                } catch (IOException e) {
310                    throw new WriteException(e);
311                }
312            }
313
314            public void append(char[] chars, int off, int len) {
315                try {
316                    writer.write(chars, off, len);
317                } catch (IOException e) {
318                    throw new WriteException(e);
319                }
320            }
321
322            public void append(String s) {
323                try {
324                    writer.write(s);
325                } catch (IOException e) {
326                    throw new WriteException(e);
327                }
328            }
329        };
330    }
331
332    private static class WriteException extends RuntimeException {
333        IOException reason;
334
335        WriteException(IOException io) {
336            reason = io;
337        }
338    }
339
340    @Override
341    public boolean equals(Object o) {
342        if (this == o) return true;
343        if (o == null || getClass() != o.getClass()) return false;
344
345        Replacer replacer = (Replacer) o;
346
347        return pattern != null ? pattern.equals(replacer.pattern) : replacer.pattern == null && (substitution != null ? substitution.equals(replacer.substitution) : replacer.substitution == null);
348
349    }
350
351    @Override
352    public int hashCode() {
353        int result = pattern != null ? pattern.hashCode() : 0;
354        result = 31 * result + (substitution != null ? substitution.hashCode() : 0);
355        return result;
356    }
357
358    @Override
359    public String toString() {
360        return "Replacer{" +
361                "pattern=" + pattern +
362                ", substitution=" + substitution +
363                '}';
364    }
365}