• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  [The "BSD license"]
3  Copyright (c) 2005-2009 Terence Parr
4  All rights reserved.
5 
6  Redistribution and use in source and binary forms, with or without
7  modification, are permitted provided that the following conditions
8  are met:
9  1. Redistributions of source code must retain the above copyright
10      notice, this list of conditions and the following disclaimer.
11  2. Redistributions in binary form must reproduce the above copyright
12      notice, this list of conditions and the following disclaimer in the
13      documentation and/or other materials provided with the distribution.
14  3. The name of the author may not be used to endorse or promote products
15      derived from this software without specific prior written permission.
16 
17  THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
18  IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
19  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
20  IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
21  INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
22  NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
23  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
24  THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
25  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
26  THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
27  */
28 package org.antlr.runtime;
29 
30 import java.util.*;
31 
32 /** Useful for dumping out the input stream after doing some
33  *  augmentation or other manipulations.
34  *
35  *  You can insert stuff, replace, and delete chunks.  Note that the
36  *  operations are done lazily--only if you convert the buffer to a
37  *  String.  This is very efficient because you are not moving data around
38  *  all the time.  As the buffer of tokens is converted to strings, the
39  *  toString() method(s) check to see if there is an operation at the
40  *  current index.  If so, the operation is done and then normal String
41  *  rendering continues on the buffer.  This is like having multiple Turing
42  *  machine instruction streams (programs) operating on a single input tape. :)
43  *
44  *  Since the operations are done lazily at toString-time, operations do not
45  *  screw up the token index values.  That is, an insert operation at token
46  *  index i does not change the index values for tokens i+1..n-1.
47  *
48  *  Because operations never actually alter the buffer, you may always get
49  *  the original token stream back without undoing anything.  Since
50  *  the instructions are queued up, you can easily simulate transactions and
51  *  roll back any changes if there is an error just by removing instructions.
52  *  For example,
53  *
54  *   CharStream input = new ANTLRFileStream("input");
55  *   TLexer lex = new TLexer(input);
56  *   TokenRewriteStream tokens = new TokenRewriteStream(lex);
57  *   T parser = new T(tokens);
58  *   parser.startRule();
59  *
60  * 	 Then in the rules, you can execute
61  *      Token t,u;
62  *      ...
63  *      input.insertAfter(t, "text to put after t");}
64  * 		input.insertAfter(u, "text after u");}
65  * 		System.out.println(tokens.toString());
66  *
67  *  Actually, you have to cast the 'input' to a TokenRewriteStream. :(
68  *
69  *  You can also have multiple "instruction streams" and get multiple
70  *  rewrites from a single pass over the input.  Just name the instruction
71  *  streams and use that name again when printing the buffer.  This could be
72  *  useful for generating a C file and also its header file--all from the
73  *  same buffer:
74  *
75  *      tokens.insertAfter("pass1", t, "text to put after t");}
76  * 		tokens.insertAfter("pass2", u, "text after u");}
77  * 		System.out.println(tokens.toString("pass1"));
78  * 		System.out.println(tokens.toString("pass2"));
79  *
80  *  If you don't use named rewrite streams, a "default" stream is used as
81  *  the first example shows.
82  */
83 public class TokenRewriteStream extends CommonTokenStream {
84 	public static final String DEFAULT_PROGRAM_NAME = "default";
85     public static final int PROGRAM_INIT_SIZE = 100;
86 	public static final int MIN_TOKEN_INDEX = 0;
87 
88 	// Define the rewrite operation hierarchy
89 
90 	class RewriteOperation {
91         /** What index into rewrites List are we? */
92         protected int instructionIndex;
93         /** Token buffer index. */
94         protected int index;
95 		protected Object text;
96 
RewriteOperation(int index)97 		protected RewriteOperation(int index) {
98 			this.index = index;
99 		}
100 
RewriteOperation(int index, Object text)101 		protected RewriteOperation(int index, Object text) {
102 			this.index = index;
103 			this.text = text;
104 		}
105 		/** Execute the rewrite operation by possibly adding to the buffer.
106 		 *  Return the index of the next token to operate on.
107 		 */
execute(StringBuffer buf)108 		public int execute(StringBuffer buf) {
109 			return index;
110 		}
toString()111 		public String toString() {
112 			String opName = getClass().getName();
113 			int $index = opName.indexOf('$');
114 			opName = opName.substring($index+1, opName.length());
115 			return "<"+opName+"@"+tokens.get(index)+
116 				   ":\""+text+"\">";
117 		}
118 	}
119 
120 	class InsertBeforeOp extends RewriteOperation {
InsertBeforeOp(int index, Object text)121 		public InsertBeforeOp(int index, Object text) {
122 			super(index,text);
123 		}
execute(StringBuffer buf)124 		public int execute(StringBuffer buf) {
125 			buf.append(text);
126 			if ( tokens.get(index).getType()!=Token.EOF ) {
127 				buf.append(tokens.get(index).getText());
128 			}
129 			return index+1;
130 		}
131 	}
132 
133 	/** I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp
134 	 *  instructions.
135 	 */
136 	class ReplaceOp extends RewriteOperation {
137 		protected int lastIndex;
ReplaceOp(int from, int to, Object text)138 		public ReplaceOp(int from, int to, Object text) {
139 			super(from,text);
140 			lastIndex = to;
141 		}
execute(StringBuffer buf)142 		public int execute(StringBuffer buf) {
143 			if ( text!=null ) {
144 				buf.append(text);
145 			}
146 			return lastIndex+1;
147 		}
toString()148 		public String toString() {
149 			if ( text==null ) {
150 				return "<DeleteOp@"+tokens.get(index)+
151 					   ".."+tokens.get(lastIndex)+">";
152 			}
153 			return "<ReplaceOp@"+tokens.get(index)+
154 				   ".."+tokens.get(lastIndex)+":\""+text+"\">";
155 		}
156 	}
157 
158 	/** You may have multiple, named streams of rewrite operations.
159 	 *  I'm calling these things "programs."
160 	 *  Maps String (name) -> rewrite (List)
161 	 */
162 	protected Map programs = null;
163 
164 	/** Map String (program name) -> Integer index */
165 	protected Map lastRewriteTokenIndexes = null;
166 
TokenRewriteStream()167 	public TokenRewriteStream() {
168 		init();
169 	}
170 
init()171 	protected void init() {
172 		programs = new HashMap();
173 		programs.put(DEFAULT_PROGRAM_NAME, new ArrayList(PROGRAM_INIT_SIZE));
174 		lastRewriteTokenIndexes = new HashMap();
175 	}
176 
TokenRewriteStream(TokenSource tokenSource)177 	public TokenRewriteStream(TokenSource tokenSource) {
178 	    super(tokenSource);
179 		init();
180 	}
181 
TokenRewriteStream(TokenSource tokenSource, int channel)182 	public TokenRewriteStream(TokenSource tokenSource, int channel) {
183 		super(tokenSource, channel);
184 		init();
185 	}
186 
rollback(int instructionIndex)187 	public void rollback(int instructionIndex) {
188 		rollback(DEFAULT_PROGRAM_NAME, instructionIndex);
189 	}
190 
191 	/** Rollback the instruction stream for a program so that
192 	 *  the indicated instruction (via instructionIndex) is no
193 	 *  longer in the stream.  UNTESTED!
194 	 */
rollback(String programName, int instructionIndex)195 	public void rollback(String programName, int instructionIndex) {
196 		List is = (List)programs.get(programName);
197 		if ( is!=null ) {
198 			programs.put(programName, is.subList(MIN_TOKEN_INDEX,instructionIndex));
199 		}
200 	}
201 
deleteProgram()202 	public void deleteProgram() {
203 		deleteProgram(DEFAULT_PROGRAM_NAME);
204 	}
205 
206 	/** Reset the program so that no instructions exist */
deleteProgram(String programName)207 	public void deleteProgram(String programName) {
208 		rollback(programName, MIN_TOKEN_INDEX);
209 	}
210 
insertAfter(Token t, Object text)211 	public void insertAfter(Token t, Object text) {
212 		insertAfter(DEFAULT_PROGRAM_NAME, t, text);
213 	}
214 
insertAfter(int index, Object text)215 	public void insertAfter(int index, Object text) {
216 		insertAfter(DEFAULT_PROGRAM_NAME, index, text);
217 	}
218 
insertAfter(String programName, Token t, Object text)219 	public void insertAfter(String programName, Token t, Object text) {
220 		insertAfter(programName,t.getTokenIndex(), text);
221 	}
222 
insertAfter(String programName, int index, Object text)223 	public void insertAfter(String programName, int index, Object text) {
224 		// to insert after, just insert before next index (even if past end)
225 		insertBefore(programName,index+1, text);
226 	}
227 
insertBefore(Token t, Object text)228 	public void insertBefore(Token t, Object text) {
229 		insertBefore(DEFAULT_PROGRAM_NAME, t, text);
230 	}
231 
insertBefore(int index, Object text)232 	public void insertBefore(int index, Object text) {
233 		insertBefore(DEFAULT_PROGRAM_NAME, index, text);
234 	}
235 
insertBefore(String programName, Token t, Object text)236 	public void insertBefore(String programName, Token t, Object text) {
237 		insertBefore(programName, t.getTokenIndex(), text);
238 	}
239 
insertBefore(String programName, int index, Object text)240 	public void insertBefore(String programName, int index, Object text) {
241 		RewriteOperation op = new InsertBeforeOp(index,text);
242 		List rewrites = getProgram(programName);
243         op.instructionIndex = rewrites.size();
244         rewrites.add(op);
245 	}
246 
replace(int index, Object text)247 	public void replace(int index, Object text) {
248 		replace(DEFAULT_PROGRAM_NAME, index, index, text);
249 	}
250 
replace(int from, int to, Object text)251 	public void replace(int from, int to, Object text) {
252 		replace(DEFAULT_PROGRAM_NAME, from, to, text);
253 	}
254 
replace(Token indexT, Object text)255 	public void replace(Token indexT, Object text) {
256 		replace(DEFAULT_PROGRAM_NAME, indexT, indexT, text);
257 	}
258 
replace(Token from, Token to, Object text)259 	public void replace(Token from, Token to, Object text) {
260 		replace(DEFAULT_PROGRAM_NAME, from, to, text);
261 	}
262 
replace(String programName, int from, int to, Object text)263 	public void replace(String programName, int from, int to, Object text) {
264 		if ( from > to || from<0 || to<0 || to >= tokens.size() ) {
265 			throw new IllegalArgumentException("replace: range invalid: "+from+".."+to+"(size="+tokens.size()+")");
266 		}
267 		RewriteOperation op = new ReplaceOp(from, to, text);
268 		List rewrites = getProgram(programName);
269         op.instructionIndex = rewrites.size();
270         rewrites.add(op);
271 	}
272 
replace(String programName, Token from, Token to, Object text)273 	public void replace(String programName, Token from, Token to, Object text) {
274 		replace(programName,
275 				from.getTokenIndex(),
276 				to.getTokenIndex(),
277 				text);
278 	}
279 
delete(int index)280 	public void delete(int index) {
281 		delete(DEFAULT_PROGRAM_NAME, index, index);
282 	}
283 
delete(int from, int to)284 	public void delete(int from, int to) {
285 		delete(DEFAULT_PROGRAM_NAME, from, to);
286 	}
287 
delete(Token indexT)288 	public void delete(Token indexT) {
289 		delete(DEFAULT_PROGRAM_NAME, indexT, indexT);
290 	}
291 
delete(Token from, Token to)292 	public void delete(Token from, Token to) {
293 		delete(DEFAULT_PROGRAM_NAME, from, to);
294 	}
295 
delete(String programName, int from, int to)296 	public void delete(String programName, int from, int to) {
297 		replace(programName,from,to,null);
298 	}
299 
delete(String programName, Token from, Token to)300 	public void delete(String programName, Token from, Token to) {
301 		replace(programName,from,to,null);
302 	}
303 
getLastRewriteTokenIndex()304 	public int getLastRewriteTokenIndex() {
305 		return getLastRewriteTokenIndex(DEFAULT_PROGRAM_NAME);
306 	}
307 
getLastRewriteTokenIndex(String programName)308 	protected int getLastRewriteTokenIndex(String programName) {
309 		Integer I = (Integer)lastRewriteTokenIndexes.get(programName);
310 		if ( I==null ) {
311 			return -1;
312 		}
313 		return I.intValue();
314 	}
315 
setLastRewriteTokenIndex(String programName, int i)316 	protected void setLastRewriteTokenIndex(String programName, int i) {
317 		lastRewriteTokenIndexes.put(programName, new Integer(i));
318 	}
319 
getProgram(String name)320 	protected List getProgram(String name) {
321 		List is = (List)programs.get(name);
322 		if ( is==null ) {
323 			is = initializeProgram(name);
324 		}
325 		return is;
326 	}
327 
initializeProgram(String name)328 	private List initializeProgram(String name) {
329 		List is = new ArrayList(PROGRAM_INIT_SIZE);
330 		programs.put(name, is);
331 		return is;
332 	}
333 
toOriginalString()334 	public String toOriginalString() {
335         fill();
336 		return toOriginalString(MIN_TOKEN_INDEX, size()-1);
337 	}
338 
toOriginalString(int start, int end)339 	public String toOriginalString(int start, int end) {
340 		StringBuffer buf = new StringBuffer();
341 		for (int i=start; i>=MIN_TOKEN_INDEX && i<=end && i<tokens.size(); i++) {
342 			if ( get(i).getType()!=Token.EOF ) buf.append(get(i).getText());
343 		}
344 		return buf.toString();
345 	}
346 
toString()347 	public String toString() {
348         fill();
349 		return toString(MIN_TOKEN_INDEX, size()-1);
350 	}
351 
toString(String programName)352 	public String toString(String programName) {
353         fill();
354 		return toString(programName, MIN_TOKEN_INDEX, size()-1);
355 	}
356 
toString(int start, int end)357 	public String toString(int start, int end) {
358 		return toString(DEFAULT_PROGRAM_NAME, start, end);
359 	}
360 
toString(String programName, int start, int end)361 	public String toString(String programName, int start, int end) {
362 		List rewrites = (List)programs.get(programName);
363 
364         // ensure start/end are in range
365         if ( end>tokens.size()-1 ) end = tokens.size()-1;
366         if ( start<0 ) start = 0;
367 
368         if ( rewrites==null || rewrites.size()==0 ) {
369 			return toOriginalString(start,end); // no instructions to execute
370 		}
371 		StringBuffer buf = new StringBuffer();
372 
373 		// First, optimize instruction stream
374 		Map indexToOp = reduceToSingleOperationPerIndex(rewrites);
375 
376         // Walk buffer, executing instructions and emitting tokens
377         int i = start;
378         while ( i <= end && i < tokens.size() ) {
379 			RewriteOperation op = (RewriteOperation)indexToOp.get(new Integer(i));
380 			indexToOp.remove(new Integer(i)); // remove so any left have index size-1
381 			Token t = (Token) tokens.get(i);
382 			if ( op==null ) {
383 				// no operation at that index, just dump token
384 				if ( t.getType()!=Token.EOF ) buf.append(t.getText());
385 				i++; // move to next token
386 			}
387 			else {
388 				i = op.execute(buf); // execute operation and skip
389 			}
390 		}
391 
392         // include stuff after end if it's last index in buffer
393         // So, if they did an insertAfter(lastValidIndex, "foo"), include
394         // foo if end==lastValidIndex.
395         if ( end==tokens.size()-1 ) {
396             // Scan any remaining operations after last token
397             // should be included (they will be inserts).
398             Iterator it = indexToOp.values().iterator();
399             while (it.hasNext()) {
400                 RewriteOperation op = (RewriteOperation)it.next();
401                 if ( op.index >= tokens.size()-1 ) buf.append(op.text);
402             }
403         }
404         return buf.toString();
405 	}
406 
407 	/** We need to combine operations and report invalid operations (like
408 	 *  overlapping replaces that are not completed nested).  Inserts to
409 	 *  same index need to be combined etc...   Here are the cases:
410 	 *
411 	 *  I.i.u I.j.v								leave alone, nonoverlapping
412 	 *  I.i.u I.i.v								combine: Iivu
413 	 *
414 	 *  R.i-j.u R.x-y.v	| i-j in x-y			delete first R
415 	 *  R.i-j.u R.i-j.v							delete first R
416 	 *  R.i-j.u R.x-y.v	| x-y in i-j			ERROR
417 	 *  R.i-j.u R.x-y.v	| boundaries overlap	ERROR
418 	 *
419 	 *  Delete special case of replace (text==null):
420 	 *  D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
421 	 *
422 	 *  I.i.u R.x-y.v | i in (x+1)-y			delete I (since insert before
423 	 *											we're not deleting i)
424 	 *  I.i.u R.x-y.v | i not in (x+1)-y		leave alone, nonoverlapping
425 	 *  R.x-y.v I.i.u | i in x-y				ERROR
426 	 *  R.x-y.v I.x.u 							R.x-y.uv (combine, delete I)
427 	 *  R.x-y.v I.i.u | i not in x-y			leave alone, nonoverlapping
428 	 *
429 	 *  I.i.u = insert u before op @ index i
430 	 *  R.x-y.u = replace x-y indexed tokens with u
431 	 *
432 	 *  First we need to examine replaces.  For any replace op:
433 	 *
434 	 * 		1. wipe out any insertions before op within that range.
435 	 *		2. Drop any replace op before that is contained completely within
436 	 *         that range.
437 	 *		3. Throw exception upon boundary overlap with any previous replace.
438 	 *
439 	 *  Then we can deal with inserts:
440 	 *
441 	 * 		1. for any inserts to same index, combine even if not adjacent.
442 	 * 		2. for any prior replace with same left boundary, combine this
443 	 *         insert with replace and delete this replace.
444 	 * 		3. throw exception if index in same range as previous replace
445 	 *
446 	 *  Don't actually delete; make op null in list. Easier to walk list.
447 	 *  Later we can throw as we add to index -> op map.
448 	 *
449 	 *  Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
450 	 *  inserted stuff would be before the replace range.  But, if you
451 	 *  add tokens in front of a method body '{' and then delete the method
452 	 *  body, I think the stuff before the '{' you added should disappear too.
453 	 *
454 	 *  Return a map from token index to operation.
455 	 */
reduceToSingleOperationPerIndex(List rewrites)456 	protected Map reduceToSingleOperationPerIndex(List rewrites) {
457 //		System.out.println("rewrites="+rewrites);
458 
459 		// WALK REPLACES
460 		for (int i = 0; i < rewrites.size(); i++) {
461 			RewriteOperation op = (RewriteOperation)rewrites.get(i);
462 			if ( op==null ) continue;
463 			if ( !(op instanceof ReplaceOp) ) continue;
464 			ReplaceOp rop = (ReplaceOp)rewrites.get(i);
465 			// Wipe prior inserts within range
466 			List inserts = getKindOfOps(rewrites, InsertBeforeOp.class, i);
467 			for (int j = 0; j < inserts.size(); j++) {
468 				InsertBeforeOp iop = (InsertBeforeOp) inserts.get(j);
469 				if ( iop.index == rop.index ) {
470 					// E.g., insert before 2, delete 2..2; update replace
471 					// text to include insert before, kill insert
472 					rewrites.set(iop.instructionIndex, null);
473 					rop.text = iop.text.toString() + (rop.text!=null?rop.text.toString():"");
474 				}
475 				else if ( iop.index > rop.index && iop.index <= rop.lastIndex ) {
476                     // delete insert as it's a no-op.
477                     rewrites.set(iop.instructionIndex, null);
478 				}
479 			}
480 			// Drop any prior replaces contained within
481 			List prevReplaces = getKindOfOps(rewrites, ReplaceOp.class, i);
482 			for (int j = 0; j < prevReplaces.size(); j++) {
483 				ReplaceOp prevRop = (ReplaceOp) prevReplaces.get(j);
484 				if ( prevRop.index>=rop.index && prevRop.lastIndex <= rop.lastIndex ) {
485                     // delete replace as it's a no-op.
486                     rewrites.set(prevRop.instructionIndex, null);
487 					continue;
488 				}
489 				// throw exception unless disjoint or identical
490 				boolean disjoint =
491 					prevRop.lastIndex<rop.index || prevRop.index > rop.lastIndex;
492 				boolean same =
493 					prevRop.index==rop.index && prevRop.lastIndex==rop.lastIndex;
494 				// Delete special case of replace (text==null):
495 				// D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
496 				if ( prevRop.text==null && rop.text==null && !disjoint ) {
497 					//System.out.println("overlapping deletes: "+prevRop+", "+rop);
498 					rewrites.set(prevRop.instructionIndex, null); // kill first delete
499 					rop.index = Math.min(prevRop.index, rop.index);
500 					rop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex);
501 					System.out.println("new rop "+rop);
502 				}
503 				else if ( !disjoint && !same ) {
504 					throw new IllegalArgumentException("replace op boundaries of "+rop+
505 													   " overlap with previous "+prevRop);
506 				}
507 			}
508 		}
509 
510 		// WALK INSERTS
511 		for (int i = 0; i < rewrites.size(); i++) {
512 			RewriteOperation op = (RewriteOperation)rewrites.get(i);
513 			if ( op==null ) continue;
514 			if ( !(op instanceof InsertBeforeOp) ) continue;
515 			InsertBeforeOp iop = (InsertBeforeOp)rewrites.get(i);
516 			// combine current insert with prior if any at same index
517 			List prevInserts = getKindOfOps(rewrites, InsertBeforeOp.class, i);
518 			for (int j = 0; j < prevInserts.size(); j++) {
519 				InsertBeforeOp prevIop = (InsertBeforeOp) prevInserts.get(j);
520 				if ( prevIop.index == iop.index ) { // combine objects
521 					// convert to strings...we're in process of toString'ing
522 					// whole token buffer so no lazy eval issue with any templates
523 					iop.text = catOpText(iop.text,prevIop.text);
524                     // delete redundant prior insert
525                     rewrites.set(prevIop.instructionIndex, null);
526 				}
527 			}
528 			// look for replaces where iop.index is in range; error
529 			List prevReplaces = getKindOfOps(rewrites, ReplaceOp.class, i);
530 			for (int j = 0; j < prevReplaces.size(); j++) {
531 				ReplaceOp rop = (ReplaceOp) prevReplaces.get(j);
532 				if ( iop.index == rop.index ) {
533 					rop.text = catOpText(iop.text,rop.text);
534 					rewrites.set(i, null);  // delete current insert
535 					continue;
536 				}
537 				if ( iop.index >= rop.index && iop.index <= rop.lastIndex ) {
538 					throw new IllegalArgumentException("insert op "+iop+
539 													   " within boundaries of previous "+rop);
540 				}
541 			}
542 		}
543 		// System.out.println("rewrites after="+rewrites);
544 		Map m = new HashMap();
545 		for (int i = 0; i < rewrites.size(); i++) {
546 			RewriteOperation op = (RewriteOperation)rewrites.get(i);
547 			if ( op==null ) continue; // ignore deleted ops
548 			if ( m.get(new Integer(op.index))!=null ) {
549 				throw new Error("should only be one op per index");
550 			}
551 			m.put(new Integer(op.index), op);
552 		}
553 		//System.out.println("index to op: "+m);
554 		return m;
555 	}
556 
catOpText(Object a, Object b)557 	protected String catOpText(Object a, Object b) {
558 		String x = "";
559 		String y = "";
560 		if ( a!=null ) x = a.toString();
561 		if ( b!=null ) y = b.toString();
562 		return x+y;
563 	}
getKindOfOps(List rewrites, Class kind)564 	protected List getKindOfOps(List rewrites, Class kind) {
565 		return getKindOfOps(rewrites, kind, rewrites.size());
566 	}
567 
568     /** Get all operations before an index of a particular kind */
getKindOfOps(List rewrites, Class kind, int before)569     protected List getKindOfOps(List rewrites, Class kind, int before) {
570 		List ops = new ArrayList();
571 		for (int i=0; i<before && i<rewrites.size(); i++) {
572 			RewriteOperation op = (RewriteOperation)rewrites.get(i);
573 			if ( op==null ) continue; // ignore deleted
574 			if ( op.getClass() == kind ) ops.add(op);
575 		}
576 		return ops;
577 	}
578 
toDebugString()579 	public String toDebugString() {
580 		return toDebugString(MIN_TOKEN_INDEX, size()-1);
581 	}
582 
toDebugString(int start, int end)583 	public String toDebugString(int start, int end) {
584 		StringBuffer buf = new StringBuffer();
585 		for (int i=start; i>=MIN_TOKEN_INDEX && i<=end && i<tokens.size(); i++) {
586 			buf.append(get(i));
587 		}
588 		return buf.toString();
589 	}
590 }
591