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