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.Serializable;
033
034/**
035 * An implementation of the Substitution interface. Performs substitutions in accordance with Perl-like substitution scripts.<br>
036 * The latter is a string, containing a mix of memory register references and plain text blocks.<br>
037 * It may look like "some_chars $1 some_chars$2some_chars" or "123${1}45${2}67".<br>
038 * A tag consisting of '$',not preceded by the escape character'\' and  followed by some digits (possibly enclosed in the curled brackets) is interpreted as a memory register reference, the digits forming a register ID.
039 * All the rest is considered as a plain text.<br>
040 * Upon the Replacer has found a text block that matches the pattern, a references in a replacement string are replaced by the contents of 
041 * corresponding memory registers, and the resulting text replaces the matched block.<br>
042 * For example, the following code:
043 * <pre>
044 * System.out.println("\""+
045 *    new Replacer(new Pattern("\\b(\\d+)\\b"),new PerlSubstitution("'$1'")).replace("abc 123 def")
046 *    +"\"");
047 * </pre>
048 * will print <code>"abc '123' def"</code>.<br>
049 * @see        Substitution
050 * @see        Replacer
051 * @see        Pattern
052 */
053
054
055public class PerlSubstitution implements Substitution, Serializable {
056    //private static Pattern refPtn,argsPtn;
057    private static final long serialVersionUID = -1537346657932720807L;
058
059    private static Pattern refPtn;
060    private static int NAME_ID;
061    private static int ESC_ID;
062    //private static int FN_NAME_ID;
063    //private static int FN_ARGS_ID;
064    //private static int ARG_NAME_ID;
065
066    private static final String groupRef = "\\$(?:\\{({=name}\\w+)\\}|({=name}\\d+|&))|\\\\({esc}.)";
067    //private static final String fnRef="\\&({fn_name}\\w+)\\(({fn_args}"+groupRef+"(?:,"+groupRef+")*)*\\)";
068
069    static {
070        try {
071            //refPtn=new Pattern("(?<!\\\\)"+fnRef+"|"+groupRef);
072            //argsPtn=new Pattern(groupRef);
073            //refPtn=new Pattern("(?<!\\\\)"+groupRef);
074            refPtn = new Pattern(groupRef);
075            NAME_ID = refPtn.groupId("name");
076            ESC_ID = refPtn.groupId("esc");
077            //ARG_NAME_ID=argsPtn.groupId("name").intValue();
078            //FN_NAME_ID=refPtn.groupId("fn_name").intValue();
079            //FN_ARGS_ID=refPtn.groupId("fn_args").intValue();
080        } catch (PatternSyntaxException e) {
081            e.printStackTrace();
082        }
083    }
084
085    private Element queueEntry;
086
087    //It seems we should somehow throw an IllegalArgumentException if an expression
088    //holds a reference to a non-existing group. Such checking will require a Pattern instance.
089    public PerlSubstitution(String s) {
090        Matcher refMatcher = new Matcher(refPtn);
091        refMatcher.setTarget(s);
092        queueEntry = makeQueue(refMatcher);
093    }
094
095    public String value(MatchResult mr) {
096        TextBuffer dest = Replacer.wrap(new StringBuilder(mr.length()));
097        appendSubstitution(mr, dest);
098        return dest.toString();
099    }
100
101    private static Element makeQueue(Matcher refMatcher) {
102        if (refMatcher.find()) {
103            Element element;
104            if (refMatcher.isCaptured(NAME_ID)) {
105                char c = refMatcher.charAt(0, NAME_ID);
106                if (c == '&') {
107                    element = new IntRefHandler(refMatcher.prefix(), 0);
108                } else if (Character.isDigit(c)) {
109                    element = new IntRefHandler(refMatcher.prefix(), new Integer(refMatcher.group(NAME_ID)));
110                } else
111                    element = new StringRefHandler(refMatcher.prefix(), refMatcher.group(NAME_ID));
112            } else {
113                //escaped char
114                element = new PlainElement(refMatcher.prefix(), refMatcher.group(ESC_ID));
115            }
116            refMatcher.setTarget(refMatcher, MatchResult.SUFFIX);
117            element.next = makeQueue(refMatcher);
118            return element;
119        } else return new PlainElement(refMatcher.target());
120    }
121
122    public void appendSubstitution(MatchResult match, TextBuffer dest) {
123        for (Element element = this.queueEntry; element != null; element = element.next) {
124            element.append(match, dest);
125        }
126    }
127
128    public String toString() {
129        StringBuilder sb = new StringBuilder();
130        for (Element element = this.queueEntry; element != null; element = element.next) {
131            sb.append(element.toString());
132        }
133        return sb.toString();
134    }
135
136    private static abstract class Element {
137        String prefix;
138        Element next;
139
140        abstract void append(MatchResult match, TextBuffer dest);
141    }
142
143    private static class PlainElement extends Element {
144        private String str;
145
146        PlainElement(String s) {
147            str = s;
148        }
149
150        PlainElement(String pref, String s) {
151            prefix = pref;
152            str = s;
153        }
154
155        void append(MatchResult match, TextBuffer dest) {
156            if (prefix != null) dest.append(prefix);
157            if (str != null) dest.append(str);
158        }
159    }
160
161    private static class IntRefHandler extends Element {
162        private Integer index;
163
164        IntRefHandler(String s, Integer ind) {
165            prefix = s;
166            index = ind;
167        }
168
169        void append(MatchResult match, TextBuffer dest) {
170            if (prefix != null) dest.append(prefix);
171            if (index == null) return;
172            int i = index;
173            if (i >= match.pattern().groupCount()) return;
174            if (match.isCaptured(i)) match.getGroup(i, dest);
175        }
176    }
177
178    private static class StringRefHandler extends Element {
179        private String index;
180
181        StringRefHandler(String s, String ind) {
182            prefix = s;
183            index = ind;
184        }
185
186        void append(MatchResult match, TextBuffer dest) {
187            if (prefix != null) dest.append(prefix);
188            if (index == null) return;
189            //if(id==null) return; //???
190            int i = match.pattern().groupId(index);
191            if (match.isCaptured(i)) match.getGroup(i, dest);
192        }
193    }
194
195    @Override
196    public boolean equals(Object o) {
197        if (this == o) return true;
198        if (o == null || getClass() != o.getClass()) return false;
199
200        PerlSubstitution that = (PerlSubstitution) o;
201
202        return queueEntry != null ? queueEntry.equals(that.queueEntry) : that.queueEntry == null;
203
204    }
205
206    @Override
207    public int hashCode() {
208        return queueEntry != null ? queueEntry.hashCode() : 0;
209    }
210}
211
212abstract class GReference {
213    public abstract String stringValue(MatchResult match);
214
215    public static GReference createInstance(MatchResult match, int grp) {
216        if (match.length(grp) == 0) throw new IllegalArgumentException("arg name cannot be an empty string");
217        if (Character.isDigit(match.charAt(0, grp))) {
218            try {
219                return new IntReference(Integer.parseInt(match.group(grp)));
220            } catch (NumberFormatException e) {
221                throw new IllegalArgumentException("illegal arg name, starts with digit but is not a number");
222            }
223        }
224        return new StringReference((match.group(grp)));
225    }
226
227}
228
229class IntReference extends GReference {
230    private int id;
231
232    IntReference(int id) {
233        this.id = id;
234    }
235
236    public String stringValue(MatchResult match) {
237        return match.group(id);
238    }
239
240    @Override
241    public boolean equals(Object o) {
242        if (this == o) return true;
243        if (o == null || getClass() != o.getClass()) return false;
244
245        IntReference that = (IntReference) o;
246
247        return id == that.id;
248
249    }
250
251    @Override
252    public int hashCode() {
253        return id;
254    }
255
256    @Override
257    public String toString() {
258        return "IntReference{" +
259                "id=" + id +
260                '}';
261    }
262}
263
264class StringReference extends GReference {
265    private String name;
266
267    StringReference(String name) {
268        this.name = name;
269    }
270
271    public String stringValue(MatchResult match) {
272        return match.group(name);
273    }
274
275    @Override
276    public boolean equals(Object o) {
277        if (this == o) return true;
278        if (o == null || getClass() != o.getClass()) return false;
279
280        StringReference that = (StringReference) o;
281
282        return name != null ? name.equals(that.name) : that.name == null;
283
284    }
285
286    @Override
287    public int hashCode() {
288        return name != null ? name.hashCode() : 0;
289    }
290
291    @Override
292    public String toString() {
293        return "StringReference{" +
294                "name='" + name + '\'' +
295                '}';
296    }
297}