001: /*BEGIN_COPYRIGHT_BLOCK
002: *
003: * Copyright (c) 2001-2007, JavaPLT group at Rice University (javaplt@rice.edu)
004: * All rights reserved.
005: *
006: * Redistribution and use in source and binary forms, with or without
007: * modification, are permitted provided that the following conditions are met:
008: * * Redistributions of source code must retain the above copyright
009: * notice, this list of conditions and the following disclaimer.
010: * * Redistributions in binary form must reproduce the above copyright
011: * notice, this list of conditions and the following disclaimer in the
012: * documentation and/or other materials provided with the distribution.
013: * * Neither the names of DrJava, the JavaPLT group, Rice University, nor the
014: * names of its contributors may be used to endorse or promote products
015: * derived from this software without specific prior written permission.
016: *
017: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
018: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
019: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
020: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR
021: * CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
022: * EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
023: * PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
024: * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
025: * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
026: * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
027: * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
028: *
029: * This software is Open Source Initiative approved Open Source Software.
030: * Open Source Initative Approved is a trademark of the Open Source Initiative.
031: *
032: * This file is part of DrJava. Download the current version of this project
033: * from http://www.drjava.org/ or http://sourceforge.net/projects/drjava/
034: *
035: * END_COPYRIGHT_BLOCK*/
036:
037: package edu.rice.cs.drjava.model.definitions.reducedmodel;
038:
039: /**
040: * A list of reduced model tokens. Uses ModelList as its base.
041: *
042: * @version $Id: TokenList.java 4255 2007-08-28 19:17:37Z mgricken $
043: */
044: public class TokenList extends ModelList<ReducedToken> implements
045: /*imports*/ReducedModelStates {
046: /**
047: * Gets a TokenList.Iterator for this list.
048: * getIterator() returns a ModelList<ReducedToken>.Iterator
049: * which is not as fully featured as a TokenList.Iterator.
050: * The underscore differentiates between the two. This
051: * differentiation was easiest since it allowed us to keep
052: * TokenList.Iterator extending ModelList<ReducedToken>.Iterator.
053: */
054: public TokenList.Iterator _getIterator() {
055: return new TokenList.Iterator();
056: }
057:
058: public class Iterator extends ModelList<ReducedToken>.Iterator {
059:
060: private int _offset;
061:
062: public Iterator() {
063: ((ModelList<ReducedToken>) TokenList.this ).super ();
064: _offset = 0;
065: }
066:
067: Iterator(Iterator that) {
068: ((ModelList<ReducedToken>) TokenList.this ).super (that);
069: _offset = that.getBlockOffset();
070: }
071:
072: /** Makes a fresh copy of this TokenList.Iterator.
073: * copy() returns a ModelList<ReducedToken>.Iterator copy
074: * which is not as fully featured as a TokenList.Iterator.
075: * The underscore differentiates between the two. This
076: * differentiation was easiest since it allowed us to keep
077: * TokenList.Iterator extending ModelList<ReducedToken>.Iterator.
078: */
079: public TokenList.Iterator _copy() {
080: return new Iterator(this );
081: }
082:
083: public void setTo(TokenList.Iterator that) {
084: super .setTo(that);
085: _offset = that.getBlockOffset();
086: }
087:
088: public int getBlockOffset() {
089: return _offset;
090: }
091:
092: public void setBlockOffset(int offset) {
093: _offset = offset;
094: }
095:
096: /**
097: * Returns the current commented/quoted state at the cursor.
098: *
099: * @return FREE|INSIDE_BLOCK_COMMENT|INSIDE_LINE_COMMENT|INSIDE_SINGLE_QUOTE|
100: * INSIDE_DOUBLE_QUOTE
101: */
102: public ReducedModelState getStateAtCurrent() {
103: if (atFirstItem() || atStart() || TokenList.this .isEmpty())
104: return FREE;
105: else if (prevItem().isLineComment()
106: || (prevItem().getState() == INSIDE_LINE_COMMENT))
107: return INSIDE_LINE_COMMENT;
108: else if (prevItem().isBlockCommentStart()
109: || (prevItem().getState() == INSIDE_BLOCK_COMMENT))
110: return INSIDE_BLOCK_COMMENT;
111: else if ((prevItem().isDoubleQuote() && prevItem().isOpen() && (prevItem()
112: .getState() == FREE))
113: || (prevItem().getState() == INSIDE_DOUBLE_QUOTE))
114: return INSIDE_DOUBLE_QUOTE;
115: else if ((prevItem().isSingleQuote() && prevItem().isOpen() && (prevItem()
116: .getState() == FREE))
117: || (prevItem().getState() == INSIDE_SINGLE_QUOTE))
118: return INSIDE_SINGLE_QUOTE;
119: else
120: return FREE;
121: }
122:
123: /** Handles the details of the case where a brace is inserted into a gap.
124: * Do not call this unless the current token is a gap!
125: */
126: void insertBraceToGap(String text) {
127: this .current().shrink(this .getBlockOffset());
128: this .insert(Brace.MakeBrace(text, getStateAtCurrent()));
129: // add a new gap to account for the remainder from the split gap
130: // if block offset is zero, do NOT add a Gap of size 0.
131: if (this .getBlockOffset() > 0) {
132: this .insert(new Gap(this .getBlockOffset(),
133: getStateAtCurrent()));
134: this .next(); //now point at new brace
135: }
136: this .next(); // now pointing at second half of gap
137: this .setBlockOffset(0);
138: }
139:
140: /** Helper function to _insertBrace.
141: * Handles the details of the case where brace is inserted between two
142: * reduced tokens. No destructive action is taken.
143: */
144: void insertNewBrace(String text) {
145: this .insert(Brace.MakeBrace(text, getStateAtCurrent()));
146: this .next();
147: this .setBlockOffset(0);
148: }
149:
150: /** Splits the current brace if it is a multiple character brace and fulfills certain conditions. If the current
151: * brace is a // or /*, split it into two braces. Do the same for star-slash (end comment block) if the parameter
152: * splitClose is true. Do the same for \\ and \" if splitEscape is true. If a split was performed, the first of
153: * the two Braces will be the current one when we're done. The offset is not changed. The two new Braces will
154: * have the same quoted/commented status as the one they were split from.
155: */
156: void _splitCurrentIfCommentBlock(boolean splitClose,
157: boolean splitEscape) {
158: String type = current().getType();
159: if (type.equals("//") || type.equals("/*")
160: || (splitClose && type.equals("*/"))
161: || (splitEscape && type.equals("\\\\"))
162: || (splitEscape && type.equals("\\\""))
163: || (splitEscape && type.equals("\\'"))) {
164: String first = type.substring(0, 1);
165: String second = type.substring(1, 2);
166: // change current Brace to only be first character
167: current().setType(first);
168: ReducedModelState oldState = current().getState();
169:
170: // then put a new brace after the current one
171: next();
172: insert(Brace.MakeBrace(second, oldState));
173: // Move back to make the first brace we inserted current
174: prev();
175: }
176: }
177:
178: /** The walk function.
179: * Walks along the list on which ReducedModel is based from the current
180: * cursor position. Which path it takes depends on the
181: * return value of getStateAtCurrent() at the start of the walk.
182: */
183: void updateBasedOnCurrentState() {
184: if (this .atStart())
185: this .next();
186:
187: // If there's no text after here, nothing to update!
188: if (this .atEnd())
189: return;
190:
191: ReducedModelState curState = this .getStateAtCurrent();
192: // Free if at the beginning
193: while (!this .atEnd()) {
194: curState = curState.update(this );
195: }
196: }
197:
198: /** Updates the BraceReduction to reflect cursor movement. Negative values move left from the cursor, positive
199: * values move right.
200: * @param count indicates the direction and magnitude of cursor movement
201: */
202: public void move(int count) {
203: _offset = _move(count, _offset);
204: }
205:
206: /** Helper function for move(int).
207: *
208: * @param count the number of chars to move. Negative values move back,
209: * positive values move forward.
210: * @param currentOffset the current offset for copyCursor
211: * @return the updated offset
212: */
213: private int _move(int count, int currentOffset) {
214: int retval = currentOffset;
215: if (count == 0)
216: return retval;
217:
218: TokenList.Iterator it = this ._copy();
219:
220: //make copy of cursor and return new iterator?
221: if (count > 0) {
222: retval = it._moveRight(count, currentOffset);
223: } else {
224: retval = it._moveLeft(Math.abs(count), currentOffset);
225: }
226: this .setTo(it);
227: it.dispose();
228: return retval;
229: }
230:
231: /** Helper function that performs forward moves.
232: * <ol>
233: * <li> at head && count>0: next
234: * <li> LOOP:<BR>
235: * if atEnd and count == 0, stop<BR>
236: * if atEnd and count > 0, throw boundary exception<BR>
237: * if count < size of current token, offset = count, stop<BR>
238: * otherwise, reduce count by size of current token and go to
239: * the next token, continuing the loop.
240: * </ol>
241: */
242: private int _moveRight(int count, int currentOffset) {
243: if (this .atStart()) {
244: currentOffset = 0;
245: this .next();
246: }
247: if (this .atEnd()) {
248: throw new IllegalArgumentException("At end");
249: }
250: while (count >= this .current().getSize() - currentOffset) {
251: count = count - this .current().getSize()
252: + currentOffset;
253: this .next();
254: currentOffset = 0;
255: if (this .atEnd()) {
256: if (count == 0)
257: break;
258: else {
259: throw new IllegalArgumentException("At end");
260: }
261: }
262: }
263: return count + currentOffset; //returns the offset
264: }
265:
266: /**
267: * Helper function that performs forward moves.
268: * <ol>
269: * <li> atEnd && count>0: prev
270: * <li> LOOP:<BR>
271: * if atStart and count == 0, stop<BR>
272: * if atStart and count > 0, throw boundary exception<BR>
273: * if count < size of current token, offset = size - count, stop<BR>
274: * otherwise, reduce count by size of current token and go to
275: * the previous token, continuing the loop.
276: * </ol>
277: */
278: private int _moveLeft(int count, int currentOffset) {
279: if (this .atEnd()) {
280: this .prev();
281: if (!this .atStart()) //make sure list not empty
282: {
283: currentOffset = this .current().getSize();
284: }
285: }
286:
287: if (this .atStart()) {
288: throw new IllegalArgumentException("At Start");
289: }
290: while (count > currentOffset) {
291: count = count - currentOffset;
292: this .prev();
293:
294: if (this .atStart()) {
295: if (count > 0) {
296: throw new IllegalArgumentException("At Start");
297: } else {
298: this .next();
299: currentOffset = 0;
300: }
301: } else {
302: currentOffset = this .current().getSize();
303: }
304: }
305: return currentOffset - count;
306: }
307:
308: /**
309: * <P>Update the BraceReduction to reflect text deletion.</P>
310: *
311: * @param count indicates the size and direction of text deletion.
312: * Negative values delete text to the left of the cursor, positive
313: * values delete text to the right.
314: * Always move count spaces to make sure we can delete.
315: */
316: public void delete(int count) {
317: if (count == 0)
318: return;
319: TokenList.Iterator copyCursor = this ._copy();
320: // from = this iterator
321: // to = this iterator's copy
322: _offset = _delete(count, copyCursor);
323: copyCursor.dispose();
324: return;
325: }
326:
327: /**
328: * Helper function for delete.
329: * If deleting forward, move delTo the distance forward and call
330: * deleteRight.<BR>
331: * If deleting backward, move delFrom the distance back and call
332: * deleteRight.
333: *
334: * @param count size of deletion
335: * @param copyCursor cursor iterator
336: * @return new offset after deletion
337: */
338: private int _delete(int count, TokenList.Iterator copyCursor) {
339: // Guarrantees that it's possible to delete count characters
340: try {
341: if (count > 0) {
342: copyCursor.move(count);
343: } else { // count <= 0
344: this .move(count);
345: }
346: return deleteRight(copyCursor);
347: } catch (Exception e) {
348: throw new IllegalArgumentException(
349: "Trying to delete past end of file.");
350: }
351: }
352:
353: /**
354: * Gets rid of extra text.
355: * Because collapse cannot get rid of all deletion text as some may be
356: * only partially spanning a token, we need to make sure that
357: * this partial span into the non-collapsed token on the left is removed.
358: */
359: void clipLeft() {
360: if (atStart()) {
361: return;
362: } else if (getBlockOffset() == 0) {
363: remove();
364: } else if (current().isGap()) {
365: int size = current().getSize();
366: this .current().shrink(size - getBlockOffset());
367: } else if (current().isMultipleCharBrace()) {
368: if (getBlockOffset() != 1) {
369: throw new IllegalArgumentException(
370: "Offset incorrect");
371: } else {
372: String type = current().getType();
373: String first = type.substring(0, 1);
374: this .current().setType(first);
375: }
376: } else {
377: throw new IllegalArgumentException("Cannot clip left.");
378: }
379: }
380:
381: /**
382: * Gets rid of extra text.
383: * Because collapse cannot get rid of all deletion text as some may be
384: * only partially spanning a token, we need to make sure that
385: * this partial span into the non-collapsed token on the right is removed.
386: */
387: void clipRight() {
388: if (this .atEnd()) {
389: return;
390: } else if (this .getBlockOffset() == 0) {
391: return;
392: } else if (this .getBlockOffset() == this .current()
393: .getSize()) {
394: this .remove();
395: } else if (this .current().isGap()) {
396: this .current().shrink(this .getBlockOffset());
397: } else if (this .current().isMultipleCharBrace()) {
398: if (this .getBlockOffset() != 1) {
399: throw new IllegalArgumentException(
400: "Offset incorrect");
401: } else {
402: String type = this .current().getType();
403: String second = type.substring(1, 2);
404: this .current().setType(second);
405: }
406: } else {
407: throw new IllegalArgumentException("Cannot clip left.");
408: }
409: }
410:
411: /** Deletes from offset in delFrom to endOffset in delTo.
412: * Uses ModelList's collapse function to facilitate quick deletion.
413: */
414: int deleteRight(TokenList.Iterator delTo) {
415: this .collapse(delTo);
416:
417: // if both pointing to same item, and it's a gap
418: if (this .eq(delTo) && this .current().isGap()) {
419: // inside gap
420: this .current().shrink(
421: delTo.getBlockOffset() - this .getBlockOffset());
422: return this .getBlockOffset();
423: }
424:
425: //if brace is multiple char it must be a comment because the above if
426: //test guarrantees it can't be a gap.
427: if (!this .eq(delTo)) {
428: this .clipLeft();
429: }
430: delTo.clipRight();
431:
432: if (!this .atStart()) {
433: this .prev();
434: }
435: int delToSizeCurr;
436: String delToTypeCurr;
437: if (delTo.atEnd()) {
438: this .setTo(delTo);
439: return 0;
440: } else {
441: delToSizeCurr = delTo.current().getSize();
442: delToTypeCurr = delTo.current().getType();
443: }
444:
445: //get info on previous item.
446: delTo.prev(); //get stats on previous item
447:
448: int delToSizePrev;
449: String delToTypePrev;
450: if (delTo.atStart()) { //no previous item, can't be at end
451: delTo.next();
452: this .setTo(delTo);
453: return 0;
454: } else {
455: delToSizePrev = delTo.current().getSize();
456: delToTypePrev = delTo.current().getType();
457: }
458: delTo.next(); //put delTo back on original node
459:
460: int temp = _calculateOffset(delToSizePrev, delToTypePrev,
461: delToSizeCurr, delToTypeCurr, delTo);
462: this .setTo(delTo);
463: return temp;
464: }
465:
466: /**
467: * By contrasting the delTo token after the walk to what it was before the
468: * walk we can see how it has changed and where the offset should go.
469: * <p/>
470: * Prev is the item previous to the current cursor
471: * Current is what the current cursor
472: * delTo is where current is pointing at this moment in time.
473: */
474: private int _calculateOffset(int delToSizePrev,
475: String delToTypePrev, int delToSizeCurr,
476: String delToTypeCurr, TokenList.Iterator delTo) {
477: int offset;
478: int delToSizeChange = delTo.current().getSize();
479: // String delToTypeChange = delTo.current().getType();
480:
481: //1)if there was a gap previous to the gap at delTo delTo should be
482: //augmented by its size, and that size is the offset.
483: //2)if the gap was not preceeded by a gap then it would not need to
484: //be shrunk
485: if (delTo.atEnd()) {
486: throw new IllegalArgumentException("Shouldn't happen");
487: }
488: if (delTo.current().isGap()) {
489: return delToSizeChange - delToSizeCurr;
490: }
491: //this means that the item at the end formed a double brace with the
492: //item that the delete left preceeding it. /dddddd*
493:
494: //the final item shrunk. This can only happen if the starting item
495: //stole one of its braces: /ddddd*/
496: //or if it was a double brace that had to get broken because it was
497: //now commented or no longer has an open block
498:
499: //EXAMPLES: /*___*/ becoming */
500: // /*___*/ delete the first star, through the spaces to get
501: // /*/
502: // //*__\n// becoming //*__//, the // is broken
503: // //*__\n// becoming //// , the // is broken
504: //THIS MUST HAVE THE previous items size and type passed in from
505: //before the update. This way we know how it's changing too.
506:
507: // In this if clause, special characters are initially separated by some text
508: // (represented here as ellipses), and when the text is deleted, the special
509: // characters come together. Sometimes, this breaks up the second token if
510: // it is a multiple character brace. Each in-line comment demonstrates
511: // the individual case that occurs and for which we check with this if.
512: // In this branch, both the cursor is off and the offset is also not correct.
513: if (((delToTypePrev.equals("/")) &&
514: // /.../* => //-*
515: ((delToTypeCurr.equals("/*") && _checkPrevEquals(
516: delTo, "//")) ||
517: // /...// => //-/
518: (delToTypeCurr.equals("//") && _checkPrevEquals(
519: delTo, "//"))))
520: ||
521:
522: ((delToTypePrev.equals("*")) &&
523: // *.../* => */-*
524: ((delToTypeCurr.equals("/*") && _checkPrevEquals(
525: delTo, "*/")) ||
526: // *...// => */-/
527: (delToTypeCurr.equals("//") && _checkPrevEquals(
528: delTo, "*/")))) ||
529:
530: ((delToTypePrev.equals("\\")) &&
531: // \...\\ => \\-\
532: ((delToTypeCurr.equals("\\\\") && _checkPrevEquals(
533: delTo, "\\"))
534: ||
535: // \...\' => \\-'
536: (delToTypeCurr.equals("\\'") && _checkPrevEquals(
537: delTo, "'")) ||
538: // \...\" => \\-"
539: (delToTypeCurr.equals("\\\"") && _checkPrevEquals(
540: delTo, "\""))))) {
541: delTo.prev();
542: offset = 1;
543: }
544: // In this branch, the cursor is on the right token, but the offset is not correct.
545: else if (((delToTypePrev.equals("/")) &&
546: // /-*/
547: ((delToTypeCurr.equals("*/") && delTo.current().getType()
548: .equals("/*"))
549: || (delToTypeCurr.equals("*") && delTo.current()
550: .getType().equals("/*")) || (delToTypeCurr
551: .equals("/") && delTo.current().getType().equals(
552: "//"))))
553: ||
554:
555: ((delToTypePrev.equals("*")) && ((delToTypeCurr
556: .equals("/") && delTo.current().getType()
557: .equals("*/"))))
558: ||
559:
560: ((delToTypePrev.equals("\\")) && ((delToTypeCurr
561: .equals("\\") && delTo.current().getType()
562: .equals("\\\\"))
563: || (delToTypeCurr.equals("'") && delTo
564: .current().getType().equals("\\'")) || (delToTypeCurr
565: .equals("\"") && delTo.current().getType()
566: .equals("\\\""))))) {
567: offset = 1;
568: }
569: // otherwise, we're on the right token and our offset is correct
570: // because no recombinations occurred
571: else {
572: offset = 0;
573: }
574: return offset;
575: }
576:
577: /**
578: * Checks if the previous token is of a certain type.
579: *
580: * @param delTo the cursor for calling prevItem on
581: * @param match the type we want to check
582: * @return true if the previous token is of type match
583: */
584: private boolean _checkPrevEquals(TokenList.Iterator delTo,
585: String match) {
586: if (delTo.atFirstItem() || delTo.atStart())
587: return false;
588: return delTo.prevItem().getType().equals(match);
589: }
590:
591: public String toString() {
592: return "" + this.current();
593: }
594:
595: }
596: }
|