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