• 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 	public 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 		}
111 		@Override
toString()112 		public String toString() {
113 			String opName = getClass().getName();
114 			int $index = opName.indexOf('$');
115 			opName = opName.substring($index+1, opName.length());
116 			return "<"+opName+"@"+tokens.get(index)+
117 				   ":\""+text+"\">";
118 		}
119 	}
120 
121 	class InsertBeforeOp extends RewriteOperation {
InsertBeforeOp(int index, Object text)122 		public InsertBeforeOp(int index, Object text) {
123 			super(index,text);
124 		}
125 		@Override
execute(StringBuffer buf)126 		public int execute(StringBuffer buf) {
127 			buf.append(text);
128 			if ( tokens.get(index).getType()!=Token.EOF ) {
129 				buf.append(tokens.get(index).getText());
130 			}
131 			return index+1;
132 		}
133 	}
134 
135 	/** I'm going to try replacing range from x..y with (y-x)+1 ReplaceOp
136 	 *  instructions.
137 	 */
138 	class ReplaceOp extends RewriteOperation {
139 		protected int lastIndex;
ReplaceOp(int from, int to, Object text)140 		public ReplaceOp(int from, int to, Object text) {
141 			super(from,text);
142 			lastIndex = to;
143 		}
144 		@Override
execute(StringBuffer buf)145 		public int execute(StringBuffer buf) {
146 			if ( text!=null ) {
147 				buf.append(text);
148 			}
149 			return lastIndex+1;
150 		}
151 		@Override
toString()152 		public String toString() {
153 			if ( text==null ) {
154 				return "<DeleteOp@"+tokens.get(index)+
155 					   ".."+tokens.get(lastIndex)+">";
156 			}
157 			return "<ReplaceOp@"+tokens.get(index)+
158 				   ".."+tokens.get(lastIndex)+":\""+text+"\">";
159 		}
160 	}
161 
162 	/** You may have multiple, named streams of rewrite operations.
163 	 *  I'm calling these things "programs."
164 	 *  Maps String (name) &rarr; rewrite (List)
165 	 */
166 	protected Map<String, List<RewriteOperation>> programs = null;
167 
168 	/** Map String (program name) &rarr; Integer index */
169 	protected Map<String, Integer> lastRewriteTokenIndexes = null;
170 
TokenRewriteStream()171 	public TokenRewriteStream() {
172 		init();
173 	}
174 
init()175 	protected void init() {
176 		programs = new HashMap<String, List<RewriteOperation>>();
177 		programs.put(DEFAULT_PROGRAM_NAME, new ArrayList<RewriteOperation>(PROGRAM_INIT_SIZE));
178 		lastRewriteTokenIndexes = new HashMap<String, Integer>();
179 	}
180 
TokenRewriteStream(TokenSource tokenSource)181 	public TokenRewriteStream(TokenSource tokenSource) {
182 	    super(tokenSource);
183 		init();
184 	}
185 
TokenRewriteStream(TokenSource tokenSource, int channel)186 	public TokenRewriteStream(TokenSource tokenSource, int channel) {
187 		super(tokenSource, channel);
188 		init();
189 	}
190 
rollback(int instructionIndex)191 	public void rollback(int instructionIndex) {
192 		rollback(DEFAULT_PROGRAM_NAME, instructionIndex);
193 	}
194 
195 	/** Rollback the instruction stream for a program so that
196 	 *  the indicated instruction (via instructionIndex) is no
197 	 *  longer in the stream.  UNTESTED!
198 	 */
rollback(String programName, int instructionIndex)199 	public void rollback(String programName, int instructionIndex) {
200 		List<RewriteOperation> is = programs.get(programName);
201 		if ( is!=null ) {
202 			programs.put(programName, is.subList(MIN_TOKEN_INDEX,instructionIndex));
203 		}
204 	}
205 
deleteProgram()206 	public void deleteProgram() {
207 		deleteProgram(DEFAULT_PROGRAM_NAME);
208 	}
209 
210 	/** Reset the program so that no instructions exist */
deleteProgram(String programName)211 	public void deleteProgram(String programName) {
212 		rollback(programName, MIN_TOKEN_INDEX);
213 	}
214 
insertAfter(Token t, Object text)215 	public void insertAfter(Token t, Object text) {
216 		insertAfter(DEFAULT_PROGRAM_NAME, t, text);
217 	}
218 
insertAfter(int index, Object text)219 	public void insertAfter(int index, Object text) {
220 		insertAfter(DEFAULT_PROGRAM_NAME, index, text);
221 	}
222 
insertAfter(String programName, Token t, Object text)223 	public void insertAfter(String programName, Token t, Object text) {
224 		insertAfter(programName,t.getTokenIndex(), text);
225 	}
226 
insertAfter(String programName, int index, Object text)227 	public void insertAfter(String programName, int index, Object text) {
228 		// to insert after, just insert before next index (even if past end)
229 		insertBefore(programName,index+1, text);
230 	}
231 
insertBefore(Token t, Object text)232 	public void insertBefore(Token t, Object text) {
233 		insertBefore(DEFAULT_PROGRAM_NAME, t, text);
234 	}
235 
insertBefore(int index, Object text)236 	public void insertBefore(int index, Object text) {
237 		insertBefore(DEFAULT_PROGRAM_NAME, index, text);
238 	}
239 
insertBefore(String programName, Token t, Object text)240 	public void insertBefore(String programName, Token t, Object text) {
241 		insertBefore(programName, t.getTokenIndex(), text);
242 	}
243 
insertBefore(String programName, int index, Object text)244 	public void insertBefore(String programName, int index, Object text) {
245 		RewriteOperation op = new InsertBeforeOp(index,text);
246 		List<? super RewriteOperation> rewrites = getProgram(programName);
247         op.instructionIndex = rewrites.size();
248         rewrites.add(op);
249 	}
250 
replace(int index, Object text)251 	public void replace(int index, Object text) {
252 		replace(DEFAULT_PROGRAM_NAME, index, index, text);
253 	}
254 
replace(int from, int to, Object text)255 	public void replace(int from, int to, Object text) {
256 		replace(DEFAULT_PROGRAM_NAME, from, to, text);
257 	}
258 
replace(Token indexT, Object text)259 	public void replace(Token indexT, Object text) {
260 		replace(DEFAULT_PROGRAM_NAME, indexT, indexT, text);
261 	}
262 
replace(Token from, Token to, Object text)263 	public void replace(Token from, Token to, Object text) {
264 		replace(DEFAULT_PROGRAM_NAME, from, to, text);
265 	}
266 
replace(String programName, int from, int to, Object text)267 	public void replace(String programName, int from, int to, Object text) {
268 		if ( from > to || from<0 || to<0 || to >= tokens.size() ) {
269 			throw new IllegalArgumentException("replace: range invalid: "+from+".."+to+"(size="+tokens.size()+")");
270 		}
271 		RewriteOperation op = new ReplaceOp(from, to, text);
272 		List<? super RewriteOperation> rewrites = getProgram(programName);
273         op.instructionIndex = rewrites.size();
274         rewrites.add(op);
275 	}
276 
replace(String programName, Token from, Token to, Object text)277 	public void replace(String programName, Token from, Token to, Object text) {
278 		replace(programName,
279 				from.getTokenIndex(),
280 				to.getTokenIndex(),
281 				text);
282 	}
283 
delete(int index)284 	public void delete(int index) {
285 		delete(DEFAULT_PROGRAM_NAME, index, index);
286 	}
287 
delete(int from, int to)288 	public void delete(int from, int to) {
289 		delete(DEFAULT_PROGRAM_NAME, from, to);
290 	}
291 
delete(Token indexT)292 	public void delete(Token indexT) {
293 		delete(DEFAULT_PROGRAM_NAME, indexT, indexT);
294 	}
295 
delete(Token from, Token to)296 	public void delete(Token from, Token to) {
297 		delete(DEFAULT_PROGRAM_NAME, from, to);
298 	}
299 
delete(String programName, int from, int to)300 	public void delete(String programName, int from, int to) {
301 		replace(programName,from,to,null);
302 	}
303 
delete(String programName, Token from, Token to)304 	public void delete(String programName, Token from, Token to) {
305 		replace(programName,from,to,null);
306 	}
307 
getLastRewriteTokenIndex()308 	public int getLastRewriteTokenIndex() {
309 		return getLastRewriteTokenIndex(DEFAULT_PROGRAM_NAME);
310 	}
311 
getLastRewriteTokenIndex(String programName)312 	protected int getLastRewriteTokenIndex(String programName) {
313 		Integer I = lastRewriteTokenIndexes.get(programName);
314 		if ( I==null ) {
315 			return -1;
316 		}
317 		return I;
318 	}
319 
setLastRewriteTokenIndex(String programName, int i)320 	protected void setLastRewriteTokenIndex(String programName, int i) {
321 		lastRewriteTokenIndexes.put(programName, i);
322 	}
323 
getProgram(String name)324 	protected List<RewriteOperation> getProgram(String name) {
325 		List<RewriteOperation> is = programs.get(name);
326 		if ( is==null ) {
327 			is = initializeProgram(name);
328 		}
329 		return is;
330 	}
331 
initializeProgram(String name)332 	private List<RewriteOperation> initializeProgram(String name) {
333 		List<RewriteOperation> is = new ArrayList<RewriteOperation>(PROGRAM_INIT_SIZE);
334 		programs.put(name, is);
335 		return is;
336 	}
337 
toOriginalString()338 	public String toOriginalString() {
339         fill();
340 		return toOriginalString(MIN_TOKEN_INDEX, size()-1);
341 	}
342 
toOriginalString(int start, int end)343 	public String toOriginalString(int start, int end) {
344 		StringBuilder buf = new StringBuilder();
345 		for (int i=start; i>=MIN_TOKEN_INDEX && i<=end && i<tokens.size(); i++) {
346 			if ( get(i).getType()!=Token.EOF ) buf.append(get(i).getText());
347 		}
348 		return buf.toString();
349 	}
350 
351 	@Override
toString()352 	public String toString() {
353         fill();
354 		return toString(MIN_TOKEN_INDEX, size()-1);
355 	}
356 
toString(String programName)357 	public String toString(String programName) {
358         fill();
359 		return toString(programName, MIN_TOKEN_INDEX, size()-1);
360 	}
361 
362 	@Override
toString(int start, int end)363 	public String toString(int start, int end) {
364 		return toString(DEFAULT_PROGRAM_NAME, start, end);
365 	}
366 
toString(String programName, int start, int end)367 	public String toString(String programName, int start, int end) {
368 		List<RewriteOperation> rewrites = programs.get(programName);
369 
370         // ensure start/end are in range
371         if ( end>tokens.size()-1 ) end = tokens.size()-1;
372         if ( start<0 ) start = 0;
373 
374         if ( rewrites==null || rewrites.isEmpty() ) {
375 			return toOriginalString(start,end); // no instructions to execute
376 		}
377 		StringBuffer buf = new StringBuffer();
378 
379 		// First, optimize instruction stream
380 		Map<Integer, ? extends RewriteOperation> indexToOp = reduceToSingleOperationPerIndex(rewrites);
381 
382         // Walk buffer, executing instructions and emitting tokens
383         int i = start;
384         while ( i <= end && i < tokens.size() ) {
385 			RewriteOperation op = indexToOp.get(i);
386 			indexToOp.remove(i); // remove so any left have index size-1
387 			Token t = tokens.get(i);
388 			if ( op==null ) {
389 				// no operation at that index, just dump token
390 				if ( t.getType()!=Token.EOF ) buf.append(t.getText());
391 				i++; // move to next token
392 			}
393 			else {
394 				i = op.execute(buf); // execute operation and skip
395 			}
396 		}
397 
398         // include stuff after end if it's last index in buffer
399         // So, if they did an insertAfter(lastValidIndex, "foo"), include
400         // foo if end==lastValidIndex.
401         if ( end==tokens.size()-1 ) {
402             // Scan any remaining operations after last token
403             // should be included (they will be inserts).
404             for (RewriteOperation op : indexToOp.values()) {
405                 if ( op.index >= tokens.size()-1 ) buf.append(op.text);
406             }
407         }
408         return buf.toString();
409 	}
410 
411 	/** We need to combine operations and report invalid operations (like
412 	 *  overlapping replaces that are not completed nested).  Inserts to
413 	 *  same index need to be combined etc...   Here are the cases:
414 	 *
415 	 *  I.i.u I.j.v								leave alone, nonoverlapping
416 	 *  I.i.u I.i.v								combine: Iivu
417 	 *
418 	 *  R.i-j.u R.x-y.v	| i-j in x-y			delete first R
419 	 *  R.i-j.u R.i-j.v							delete first R
420 	 *  R.i-j.u R.x-y.v	| x-y in i-j			ERROR
421 	 *  R.i-j.u R.x-y.v	| boundaries overlap	ERROR
422 	 *
423 	 *  Delete special case of replace (text==null):
424 	 *  D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
425 	 *
426 	 *  I.i.u R.x-y.v | i in (x+1)-y			delete I (since insert before
427 	 *											we're not deleting i)
428 	 *  I.i.u R.x-y.v | i not in (x+1)-y		leave alone, nonoverlapping
429 	 *  R.x-y.v I.i.u | i in x-y				ERROR
430 	 *  R.x-y.v I.x.u 							R.x-y.uv (combine, delete I)
431 	 *  R.x-y.v I.i.u | i not in x-y			leave alone, nonoverlapping
432 	 *
433 	 *  I.i.u = insert u before op @ index i
434 	 *  R.x-y.u = replace x-y indexed tokens with u
435 	 *
436 	 *  First we need to examine replaces.  For any replace op:
437 	 *
438 	 * 		1. wipe out any insertions before op within that range.
439 	 *		2. Drop any replace op before that is contained completely within
440 	 *         that range.
441 	 *		3. Throw exception upon boundary overlap with any previous replace.
442 	 *
443 	 *  Then we can deal with inserts:
444 	 *
445 	 * 		1. for any inserts to same index, combine even if not adjacent.
446 	 * 		2. for any prior replace with same left boundary, combine this
447 	 *         insert with replace and delete this replace.
448 	 * 		3. throw exception if index in same range as previous replace
449 	 *
450 	 *  Don't actually delete; make op null in list. Easier to walk list.
451 	 *  Later we can throw as we add to index &rarr; op map.
452 	 *
453 	 *  Note that I.2 R.2-2 will wipe out I.2 even though, technically, the
454 	 *  inserted stuff would be before the replace range.  But, if you
455 	 *  add tokens in front of a method body '{' and then delete the method
456 	 *  body, I think the stuff before the '{' you added should disappear too.
457 	 *
458 	 *  Return a map from token index to operation.
459 	 */
reduceToSingleOperationPerIndex(List<? extends RewriteOperation> rewrites)460 	protected Map<Integer, ? extends RewriteOperation> reduceToSingleOperationPerIndex(List<? extends RewriteOperation> rewrites) {
461 //		System.out.println("rewrites="+rewrites);
462 
463 		// WALK REPLACES
464 		for (int i = 0; i < rewrites.size(); i++) {
465 			RewriteOperation op = rewrites.get(i);
466 			if ( op==null ) continue;
467 			if ( !(op instanceof ReplaceOp) ) continue;
468 			ReplaceOp rop = (ReplaceOp)rewrites.get(i);
469 			// Wipe prior inserts within range
470 			List<? extends InsertBeforeOp> inserts = getKindOfOps(rewrites, InsertBeforeOp.class, i);
471 			for (int j = 0; j < inserts.size(); j++) {
472 				InsertBeforeOp iop = inserts.get(j);
473 				if ( iop.index == rop.index ) {
474 					// E.g., insert before 2, delete 2..2; update replace
475 					// text to include insert before, kill insert
476 					rewrites.set(iop.instructionIndex, null);
477 					rop.text = iop.text.toString() + (rop.text!=null?rop.text.toString():"");
478 				}
479 				else if ( iop.index > rop.index && iop.index <= rop.lastIndex ) {
480                     // delete insert as it's a no-op.
481                     rewrites.set(iop.instructionIndex, null);
482 				}
483 			}
484 			// Drop any prior replaces contained within
485 			List<? extends ReplaceOp> prevReplaces = getKindOfOps(rewrites, ReplaceOp.class, i);
486 			for (int j = 0; j < prevReplaces.size(); j++) {
487 				ReplaceOp prevRop = prevReplaces.get(j);
488 				if ( prevRop.index>=rop.index && prevRop.lastIndex <= rop.lastIndex ) {
489                     // delete replace as it's a no-op.
490                     rewrites.set(prevRop.instructionIndex, null);
491 					continue;
492 				}
493 				// throw exception unless disjoint or identical
494 				boolean disjoint =
495 					prevRop.lastIndex<rop.index || prevRop.index > rop.lastIndex;
496 				boolean same =
497 					prevRop.index==rop.index && prevRop.lastIndex==rop.lastIndex;
498 				// Delete special case of replace (text==null):
499 				// D.i-j.u D.x-y.v	| boundaries overlap	combine to max(min)..max(right)
500 				if ( prevRop.text==null && rop.text==null && !disjoint ) {
501 					//System.out.println("overlapping deletes: "+prevRop+", "+rop);
502 					rewrites.set(prevRop.instructionIndex, null); // kill first delete
503 					rop.index = Math.min(prevRop.index, rop.index);
504 					rop.lastIndex = Math.max(prevRop.lastIndex, rop.lastIndex);
505 					System.out.println("new rop "+rop);
506 				}
507 				else if ( !disjoint && !same ) {
508 					throw new IllegalArgumentException("replace op boundaries of "+rop+
509 													   " overlap with previous "+prevRop);
510 				}
511 			}
512 		}
513 
514 		// WALK INSERTS
515 		for (int i = 0; i < rewrites.size(); i++) {
516 			RewriteOperation op = rewrites.get(i);
517 			if ( op==null ) continue;
518 			if ( !(op instanceof InsertBeforeOp) ) continue;
519 			InsertBeforeOp iop = (InsertBeforeOp)rewrites.get(i);
520 			// combine current insert with prior if any at same index
521 			List<? extends InsertBeforeOp> prevInserts = getKindOfOps(rewrites, InsertBeforeOp.class, i);
522 			for (int j = 0; j < prevInserts.size(); j++) {
523 				InsertBeforeOp prevIop = prevInserts.get(j);
524 				if ( prevIop.index == iop.index ) { // combine objects
525 					// convert to strings...we're in process of toString'ing
526 					// whole token buffer so no lazy eval issue with any templates
527 					iop.text = catOpText(iop.text,prevIop.text);
528                     // delete redundant prior insert
529                     rewrites.set(prevIop.instructionIndex, null);
530 				}
531 			}
532 			// look for replaces where iop.index is in range; error
533 			List<? extends ReplaceOp> prevReplaces = getKindOfOps(rewrites, ReplaceOp.class, i);
534 			for (int j = 0; j < prevReplaces.size(); j++) {
535 				ReplaceOp rop = prevReplaces.get(j);
536 				if ( iop.index == rop.index ) {
537 					rop.text = catOpText(iop.text,rop.text);
538 					rewrites.set(i, null);  // delete current insert
539 					continue;
540 				}
541 				if ( iop.index >= rop.index && iop.index <= rop.lastIndex ) {
542 					throw new IllegalArgumentException("insert op "+iop+
543 													   " within boundaries of previous "+rop);
544 				}
545 			}
546 		}
547 		// System.out.println("rewrites after="+rewrites);
548 		Map<Integer, RewriteOperation> m = new HashMap<Integer, RewriteOperation>();
549 		for (int i = 0; i < rewrites.size(); i++) {
550 			RewriteOperation op = rewrites.get(i);
551 			if ( op==null ) continue; // ignore deleted ops
552 			if ( m.get(op.index)!=null ) {
553 				throw new Error("should only be one op per index");
554 			}
555 			m.put(op.index, op);
556 		}
557 		//System.out.println("index to op: "+m);
558 		return m;
559 	}
560 
catOpText(Object a, Object b)561 	protected String catOpText(Object a, Object b) {
562 		String x = "";
563 		String y = "";
564 		if ( a!=null ) x = a.toString();
565 		if ( b!=null ) y = b.toString();
566 		return x+y;
567 	}
getKindOfOps(List<? extends RewriteOperation> rewrites, Class<T> kind)568 	protected <T extends RewriteOperation> List<? extends T> getKindOfOps(List<? extends RewriteOperation> rewrites, Class<T> kind) {
569 		return getKindOfOps(rewrites, kind, rewrites.size());
570 	}
571 
572     /** Get all operations before an index of a particular kind */
getKindOfOps(List<? extends RewriteOperation> rewrites, Class<T> kind, int before)573     protected <T extends RewriteOperation> List<? extends T> getKindOfOps(List<? extends RewriteOperation> rewrites, Class<T> kind, int before) {
574 		List<T> ops = new ArrayList<T>();
575 		for (int i=0; i<before && i<rewrites.size(); i++) {
576 			RewriteOperation op = rewrites.get(i);
577 			if ( op==null ) continue; // ignore deleted
578 			if ( kind.isInstance(op) ) ops.add(kind.cast(op));
579 		}
580 		return ops;
581 	}
582 
toDebugString()583 	public String toDebugString() {
584 		return toDebugString(MIN_TOKEN_INDEX, size()-1);
585 	}
586 
toDebugString(int start, int end)587 	public String toDebugString(int start, int end) {
588 		StringBuilder buf = new StringBuilder();
589 		for (int i=start; i>=MIN_TOKEN_INDEX && i<=end && i<tokens.size(); i++) {
590 			buf.append(get(i));
591 		}
592 		return buf.toString();
593 	}
594 }
595