JAL-1807 Bob's JalviewJS prototype first commit
[jalviewjs.git] / src / com / stevesoft / pat / ReplaceRule.java
1 //
2 // This software is now distributed according to
3 // the Lesser Gnu Public License.  Please see
4 // http://www.gnu.org/copyleft/lesser.txt for
5 // the details.
6 //    -- Happy Computing!
7 //
8 package com.stevesoft.pat;
9
10 import java.util.*;
11
12 /**
13  * ReplaceRule is a singly linked list of Objects which describe how to replace
14  * the matched portion of a String. The only member method that you absolutely
15  * need to define to use this class is apply(StringBuffer,RegRes) -- although
16  * you may want define toString1() and clone1() (if you are unhappy with the
17  * default methods) that are needed by the clone() or toString() methods on this
18  * class. During the replacement process, each ReplaceRule tells the replacer
19  * what to add to StringBuffer and uses the contents of the Regular expression
20  * result to get the information it needs to do this. Here is an <a
21  * href="http://javaregex.com/code/fancy.java.html">example</a>
22  * 
23  * @see com.stevesoft.pat.NullRule
24  * @see com.stevesoft.pat.AmpersandRule
25  * @see com.stevesoft.pat.BackRefRule
26  * @see com.stevesoft.pat.LeftRule
27  * @see com.stevesoft.pat.RightRule
28  * @see com.stevesoft.pat.StringRule
29  */
30 public abstract class ReplaceRule
31 {
32   /** points to the next ReplaceRule in the linked list. */
33   protected ReplaceRule next = null;
34
35   /**
36    * This function appends to the StringBufferLike the text you want to replaced
37    * the portion of the String last matched.
38    */
39   public abstract void apply(StringBufferLike sb, RegRes r);
40
41   /**
42    * A rule describing how to clone only the current ReplaceRule, and none of
43    * the others in this linked list. It is called by clone() for each item in
44    * the list.
45    */
46   public Object clone1()
47   {
48     return new RuleHolder(this);
49   }
50
51   public final Object clone()
52   {
53     ReplaceRule x = (ReplaceRule) clone1();
54     ReplaceRule xsav = x;
55     ReplaceRule y = this;
56     while (y.next != null)
57     {
58       x.next = (ReplaceRule) y.next.clone1();
59       x.name = y.name;
60       x = x.next;
61       y = y.next;
62     }
63     return xsav;
64   }
65
66   static ReplaceRule add(ReplaceRule head, ReplaceRule adding)
67   {
68     if (head == null)
69     {
70       return head = adding;
71     }
72     head.addRule(adding);
73     return head;
74   }
75
76   public ReplaceRule add(ReplaceRule adding)
77   {
78     return add(this, adding);
79   }
80
81   /** Add another ReplaceRule to the linked list. */
82   public void addRule(ReplaceRule r)
83   {
84     if (next == null)
85     {
86       next = r;
87     }
88     else
89     {
90       next.addRule(r);
91     }
92   }
93
94   static Regex getvar = null;
95
96   private static Regex getv()
97   {
98     // Thanks to Michael Jimenez for pointing out the need
99     // to clone getvar rather than simply returning it.
100     // Previously this was not thread safe.
101     // if(getvar != null) return getvar;
102     if (getvar != null)
103     {
104       return (Regex) getvar.clone();
105     }
106     getvar = new Regex("(?:\\\\(\\d+)|" + // ref 1
107             "\\$(?:" + "(\\d+)|" + // ref 2
108             "(\\w+)|" + // ref 3
109             "([&'`])|" + // ref 4
110             "\\{(?:(\\d+)|" + // ref 5
111             "([^\n}\\\\]+))}" + // ref 6
112             ")|" + "\\\\([nrbtaef])|" + // ref 7
113             "\\\\c([\u0000-\uFFFF])|" + // ref 8
114             "\\\\x([A-Fa-f0-9]{2})|" + // ref 9
115             "\\\\([\u0000-\uFFFF])" + // ref 10
116             ")", "");
117     getvar.optimize();
118     return getvar;
119   }
120
121   /**
122    * Compile a ReplaceRule using the text that would go between the second and
123    * third /'s in a typical substitution pattern in Perl: s/ ... / <i>The
124    * argument to ReplaceRule.perlCode</i> /.
125    */
126   public static ReplaceRule perlCode(String s)
127   {
128     // String sav_backGs = Regex.backGs;
129     // int sav_backGto = Regex.backGto;
130     try
131     {
132       int mf = 0, mt = 0;
133       Regex gv = getv();
134       ReplaceRule head = null;
135       Object tmp = null;
136       while (gv.searchFrom(s, mt))
137       {
138         int off = Regex.BackRefOffset - 1;
139         mf = gv.matchedFrom();
140         if (mf > mt)
141         {
142           head = add(head, new StringRule(s.substring(mt, mf)));
143         }
144         String var = null;
145         if ((var = gv.stringMatchedI(1 + off)) != null
146                 || (var = gv.stringMatchedI(2 + off)) != null
147                 || (var = gv.stringMatchedI(5 + off)) != null)
148         {
149           int d = 0;
150           for (int i = 0; i < var.length(); i++)
151           {
152             d = 8 * d + (var.charAt(i) - '0');
153           }
154           if (var.length() == 1)
155           {
156             head = add(head, new BackRefRule(d));
157           }
158           else
159           {
160             head = new StringRule("" + (char) d);
161           }
162         }
163         else if ((var = gv.stringMatchedI(10 + off)) != null)
164         {
165           if ("QELlUu".indexOf(var) >= 0)
166           {
167             head = add(head, new CodeRule(var.charAt(0)));
168           }
169           else
170           {
171             head = add(head, new StringRule(var));
172           }
173         }
174         else if ((var = gv.stringMatchedI(3 + off)) != null
175                 || (var = gv.stringMatchedI(4 + off)) != null
176                 || (var = gv.stringMatchedI(6 + off)) != null)
177         {
178           String arg = "";
179           int pc;
180           if ((pc = var.indexOf(':')) > 0)
181           {
182             arg = var.substring(pc + 1);
183             var = var.substring(0, pc);
184           }
185           if (var.equals("&") || var.equals("MATCH"))
186           {
187             head = add(head, new AmpersandRule());
188           }
189           else if (var.equals("`") || var.equals("PREMATCH"))
190           {
191             head = add(head, new LeftRule());
192           }
193           else if (var.equals("'") || var.equals("POSTMATCH"))
194           {
195             head = add(head, new RightRule());
196           }
197           else if (var.equals("WANT_MORE_TEXT"))
198           {
199             head = add(head, new WantMoreTextReplaceRule());
200           }
201           else if (var.equals("POP"))
202           {
203             head = add(head, new PopRule());
204           }
205           else if (var.startsWith("+")
206                   && (tmp = defs.get(var.substring(1))) != null)
207           {
208             if (tmp instanceof Regex)
209             {
210               head = add(head, new PushRule(var.substring(1), (Regex) tmp));
211             }
212             else if (tmp instanceof Transformer)
213             {
214               head = add(head, new PushRule(var.substring(1),
215                       (Transformer) tmp));
216             }
217             else
218             {
219               head = add(head, new StringRule("${" + var + "}"));
220             }
221           }
222           else if (var.startsWith("=")
223                   && (tmp = defs.get(var.substring(1))) != null)
224           {
225             if (tmp instanceof Regex)
226             {
227               head = add(head,
228                       new ChangeRule(var.substring(1), (Regex) tmp));
229             }
230             else if (tmp instanceof Transformer)
231             {
232               head = add(head, new ChangeRule(var.substring(1),
233                       (Transformer) tmp));
234             }
235             else
236             {
237               head = add(head, new StringRule("${" + var + "}"));
238             }
239           }
240           else if ((tmp = defs.get(var)) != null)
241           {
242             if (tmp instanceof ReplaceRule)
243             {
244               ReplaceRule alt = ((ReplaceRule) tmp).arg(arg);
245               if (alt == null)
246               {
247                 alt = ((ReplaceRule) tmp);
248               }
249               head = add(head, (ReplaceRule) (alt.clone()));
250             }
251           }
252           else
253           // can't figure out how to transform this thing...
254           {
255             head = add(head, new StringRule("${" + var + "}"));
256           }
257         }
258         else if ((var = gv.stringMatchedI(7 + off)) != null)
259         {
260           char c = var.charAt(0);
261           if (c == 'n')
262           {
263             head = add(head, new StringRule("\n"));
264           }
265           else if (c == 't')
266           {
267             head = add(head, new StringRule("\t"));
268           }
269           else if (c == 'r')
270           {
271             head = add(head, new StringRule("\r"));
272           }
273           else if (c == 'b')
274           {
275             head = add(head, new StringRule("\r"));
276           }
277           else if (c == 'a')
278           {
279             head = add(head, new StringRule("" + (char) 7));
280           }
281           else if (c == 'e')
282           {
283             head = add(head, new StringRule("" + (char) 27));
284           }
285           else if (c == 'f')
286           {
287             head = add(head, new StringRule("" + (char) 12));
288           }
289         }
290         else if ((var = gv.stringMatchedI(8 + off)) != null)
291         {
292           char c = var.charAt(0);
293           if (c < Ctrl.cmap.length)
294           {
295             c = Ctrl.cmap[c];
296           }
297           head = add(head, new StringRule("" + c));
298         }
299         else if ((var = gv.stringMatchedI(9 + off)) != null)
300         {
301           int d = 16 * getHexDigit(var.charAt(0))
302                   + getHexDigit(var.charAt(1));
303           head = add(head, new StringRule("" + (char) d));
304         }
305         mt = gv.matchedTo();
306       }
307       if (mt <= s.length())
308       {
309         head = add(head, new StringRule(s.substring(mt)));
310       }
311       return head;
312     } finally
313     {
314       // Regex.backGs = sav_backGs;
315       // Regex.backGto = sav_backGto;
316     }
317   }
318
319   static Hashtable defs = new Hashtable();
320
321   public static boolean isDefined(String s)
322   {
323     return defs.get(s) != null;
324   }
325
326   public static void define(String s, Regex r)
327   {
328     defs.put(s, r);
329   }
330
331   public static void define(String s, ReplaceRule r)
332   {
333     defs.put(s, r);
334     r.name = s;
335   }
336
337   String name = getClass().getName();
338
339   public static void define(String s, Transformer t)
340   {
341     defs.put(s, t);
342   }
343
344   public static void undefine(String s)
345   {
346     defs.remove(s);
347   }
348
349   /**
350    * This tells how to convert just the current element (and none of the other
351    * items in the linked list) to a String. This method is called by toString()
352    * for each item in the linked list.
353    */
354   public String toString1()
355   {
356     return "${" + name + "}";
357   }
358
359   /** Convert to a String. */
360   public final String toString()
361   {
362     StringBuffer sb = new StringBuffer();
363     sb.append(toString1());
364     ReplaceRule rr = this.next;
365     while (rr != null)
366     {
367       sb.append(rr.toString1());
368       rr = rr.next;
369     }
370     return sb.toString();
371   }
372
373   /**
374    * Modified the behavior of a ReplaceRule by supplying an argument. If a
375    * ReplaceRule named "foo" is defined and the pattern "s/x/${foo:5}/" is given
376    * to Regex.perlCode, then the "foo" the definition of "foo" will be retrieved
377    * and arg("5") will be called. If the result is non-null, that is the
378    * ReplaceRule that will be used. If the result is null, then the pattern
379    * works just as if it were "s/x/${foo}/".
380    * 
381    * @see com.stevesoft.pat.Validator#arg(java.lang.String)
382    */
383   public ReplaceRule arg(String s)
384   {
385     return null;
386   }
387
388   static int getHexDigit(char c)
389   {
390     if (c >= '0' && c <= '9')
391     {
392       return c - '0';
393     }
394     if (c >= 'a' && c <= 'f')
395     {
396       return c - 'a' + 10;
397     }
398     return c - 'A' + 10;
399   }
400 }