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: import java.util.Vector;
040:
041: import edu.rice.cs.util.UnexpectedException;
042:
043: /** This class provides an implementation of the BraceReduction interface for brace matching. In order to correctly
044: * match, this class keeps track of what is commented (line and block) and what is inside double quotes and keeps
045: * this in mind when matching. To avoid unnecessary complication, this class maintains a few invariants for its
046: * consistent states, i.e., between top-level function calls.
047: * <ol>
048: * <li> The cursor offset is never at the end of a brace. If movement or insertion puts it there, the cursor is
049: * updated to point to the 0 offset of the next brace.
050: * <li> Quoting information is invalid inside valid comments. When part of the document becomes uncommented, the
051: * reduced model must update the quoting information linearly in the newly revealed code.
052: * <li> Quote shadowing and comment shadowing are mutually exclusive.
053: * <li> There is no nesting of comment open characters. If // is encountered in the middle of a comment, it is
054: * treated as two separate slashes. Similar for /*.
055: * </ol>
056: * All of the code in the class assumes that a lock on this is held.
057: * @author JavaPLT
058: * @version $Id: ReducedModelControl.java 4255 2007-08-28 19:17:37Z mgricken $
059: */
060: public class ReducedModelControl implements BraceReduction {
061: /* private fields; default visibility for testing purposes only. */
062: volatile ReducedModelBrace _rmb;
063: volatile ReducedModelComment _rmc;
064: volatile int _offset;
065:
066: // Relying on default constructor
067: // public ReducedModelControl() {
068: // rmb = new ReducedModelBrace(this);
069: // rmc = new ReducedModelComment();
070: // }
071:
072: private ReducedModelBrace getRMB() {
073: if (_rmb == null)
074: _rmb = new ReducedModelBrace(this );
075: return _rmb;
076: }
077:
078: private ReducedModelComment getRMC() {
079: if (_rmc == null)
080: _rmc = new ReducedModelComment();
081: return _rmc;
082: }
083:
084: public void insertChar(char ch) {
085: getRMB().insertChar(ch);
086: getRMC().insertChar(ch);
087: }
088:
089: /** <P>Updates the BraceReduction to reflect cursor movement. Negative values move left from the cursor,
090: * positive values move right. </P>
091: * @param count indicates the direction and magnitude of cursor movement
092: */
093: public void move(int count) {
094: try {
095: getRMB().move(count);
096: getRMC().move(count);
097: } catch (IllegalArgumentException e) {
098: resetLocation();
099: throw new UnexpectedException(e);
100: }
101: }
102:
103: /** <P>Update the BraceReduction to reflect text deletion.</P>
104: * @param count indicates the size and direction of text deletion. Negative values delete text to the left of the
105: * cursor, positive values delete text to the right.
106: */
107: public void delete(int count) {
108: getRMB().delete(count);
109: getRMC().delete(count);
110: }
111:
112: /** <P>Finds the closing brace that matches the next significant brace iff that brace is an open brace.</P>
113: * @return the distance until the matching closing brace. On failure, returns -1.
114: * @see #balanceBackward()
115: */
116: public int balanceForward() {
117: return getRMB().balanceForward();
118: }
119:
120: /** <P>Finds the open brace that matches the previous significant brace iff that brace is an closing brace.</P>
121: * @return the distance until the matching open brace. On failure, returns -1.
122: * @see #balanceForward()
123: */
124: public int balanceBackward() {
125: return getRMB().balanceBackward();
126: }
127:
128: /** This function returns the state at the relDistance, where relDistance is relative to the last time it was called.
129: * You can reset the last call to the current offset using resetLocation.
130: */
131: public ReducedModelState moveWalkerGetState(int relDistance) {
132: return getRMC().moveWalkerGetState(relDistance);
133: }
134:
135: /** This function resets the location of the walker in the comment list to
136: * where the current cursor is. This allows the walker to keep walking and
137: * using relative distance instead of having to rewalk the same distance
138: * every call to stateAtRelLocation. It is an optimization.
139: */
140: public void resetLocation() {
141: getRMC().resetWalkerLocationToCursor();
142: }
143:
144: /** Get the token currently pointed at by the cursor. Because the reduced model is split into two reduced sub-models,
145: * we have to check each sub-model first as each one has unique information. If we find a non-gap token in either
146: * sub-model we want to return that. Otherwise, we want to return a sort of hybrid Gap of the two, i.e., a Gap where
147: * there are neither special comment/quote tokens nor parens/squigglies/brackets.
148: * @return a ReducedToken representative of the unified reduced model
149: */
150: public ReducedToken currentToken() {
151: // check the reduced comment model for specials
152: ReducedToken rmcToken = getRMC().current();
153: if (!rmcToken.isGap())
154: return rmcToken;
155: // check the reduced brace model for braces
156: ReducedToken rmbToken = getRMB().current();
157: if (!rmbToken.isGap()) {
158: rmbToken.setState(getRMC().getStateAtCurrent());
159: return rmbToken;
160: }
161: // otherwise, we have a gap.
162: int size = getSize(rmbToken, rmcToken);
163: return new Gap(size, getRMC().getStateAtCurrent());
164: }
165:
166: /** Get the shadowing state at the current caret position.
167: * @return FREE|INSIDE_LINE_COMMENT|INSIDE_BLOCK_COMMENT|
168: * INSIDE_SINGLE_QUOTE|INSIDE_DOUBLE_QUOTE
169: */
170: public ReducedModelState getStateAtCurrent() {
171: return getRMC().getStateAtCurrent();
172: }
173:
174: /**
175: * Get a string representation of the current token's type.
176: * @return "" if current is a Gap, otherwise, use ReducedToken.getType()
177: */
178: String getType() {
179: ReducedToken rmcToken = getRMC().current();
180: if (!rmcToken.isGap())
181: return rmcToken.getType();
182:
183: ReducedToken rmbToken = getRMB().current();
184: if (!rmbToken.isGap()) {
185: return rmbToken.getType();
186: }
187: return ""; //a gap
188: }
189:
190: /**
191: * Gets the size of the current token.
192: * It checks both the brace and comment sub-models to find the size
193: * of the current token. If the current token is a Gap, we have to reconcile
194: * the information of both sub-models in order to get the correct size
195: * of the current token as seen by the outside world.
196: * @return the number of characters represented by the current token
197: */
198: int getSize() {
199: return getSize(getRMB().current(), getRMC().current());
200: }
201:
202: int getSize(ReducedToken rmbToken, ReducedToken rmcToken) {
203: int rmb_offset = getRMB().getBlockOffset();
204: int rmc_offset = getRMC().getBlockOffset();
205: int rmb_size = rmbToken.getSize();
206: int rmc_size = rmcToken.getSize();
207: int size;
208: if (rmb_offset < rmc_offset) {
209: size = rmb_offset;
210: _offset = size;
211: } else {
212: size = rmc_offset;
213: _offset = size;
214: }
215:
216: if (rmb_size - rmb_offset < rmc_size - rmc_offset) {
217: size += (rmb_size - rmb_offset);
218: } else {
219: size += (rmc_size - rmc_offset);
220: }
221: return size;
222: }
223:
224: /**
225: * Move the reduced model to the next token and update the cursor information.
226: */
227: void next() {
228: if (getRMC()._cursor.atStart()) {
229: getRMC().next();
230: getRMB().next();
231: return;
232: }
233: int size = getSize(getRMB().current(), getRMC().current());
234: getRMC().move(size - _offset);
235: getRMB().move(size - _offset);
236: }
237:
238: /**
239: * Move the reduced model to the previous token and update the cursor information.
240: */
241: void prev() {
242: int size;
243: if (getRMC()._cursor.atEnd()) {
244: getRMC().prev();
245: getRMB().prev();
246: if (getRMC()._cursor.atStart()) {
247: return; // because in place now.
248: }
249:
250: if (getRMC().current().getSize() < getRMB().current()
251: .getSize()) {
252: size = -getRMC().current().getSize();
253: } else {
254: size = -getRMB().current().getSize();
255: }
256: getRMC().next();
257: getRMB().next();
258: move(size);
259: } else if (getRMB().getBlockOffset() < getRMC()
260: .getBlockOffset()) {
261: getRMB().prev();
262: size = getRMB().current().getSize()
263: + getRMB().getBlockOffset();
264: getRMB().next();
265: if (size < getRMC().getBlockOffset()) {
266: move(-size);
267: } else {
268: move(-getRMC().getBlockOffset());
269: }
270: } else if (getRMB().getBlockOffset() == getRMC()
271: .getBlockOffset()) {
272: getRMB().prev();
273: getRMC().prev();
274: getRMB().setBlockOffset(0);
275: getRMC().setBlockOffset(0);
276: } else {
277: getRMC().prev();
278: size = getRMC().current().getSize()
279: + getRMC().getBlockOffset();
280: getRMC().next();
281: if (size < getRMB().getBlockOffset()) {
282: move(-size);
283: } else {
284: move(-getRMB().getBlockOffset());
285: }
286: }
287: }
288:
289: /**
290: * Get the previous token.
291: */
292: public ReducedToken prevItem() {
293: int rmbOffset = getRMB().getBlockOffset();
294: int rmcOffset = getRMC().getBlockOffset();
295:
296: prev();
297: ReducedToken temp = currentToken();
298: next();
299:
300: getRMB().setBlockOffset(rmbOffset);
301: getRMC().setBlockOffset(rmcOffset);
302: return temp;
303: }
304:
305: /**
306: * Get the next token.
307: */
308: public ReducedToken nextItem() {
309: int rmbOffset = getRMB().getBlockOffset();
310: int rmcOffset = getRMC().getBlockOffset();
311: next();
312: ReducedToken temp = currentToken();
313: prev();
314: getRMB().setBlockOffset(rmbOffset);
315: getRMC().setBlockOffset(rmcOffset);
316: return temp;
317: }
318:
319: /** Determines if the cursor is at the end of the reduced model. */
320: boolean atEnd() {
321: return (getRMB()._cursor.atEnd() || getRMC()._cursor.atEnd());
322: }
323:
324: /** Determines if the cursor is at the start of the reduced model. */
325: boolean atStart() {
326: return (getRMB()._cursor.atStart() || getRMC()._cursor
327: .atStart());
328: }
329:
330: /** Gets the offset within the current token. */
331: int getBlockOffset() {
332: if (getRMB().getBlockOffset() < getRMC().getBlockOffset())
333: return getRMB().getBlockOffset();
334: return getRMC().getBlockOffset();
335: }
336:
337: /** Gets the absolute character offset into the document represented by the reduced model. */
338: public int absOffset() {
339: return getRMC().absOffset();
340: }
341:
342: /** A toString() substitute. */
343: public String simpleString() {
344: return "\n********\n" + getRMB().simpleString()
345: + "\n________\n" + getRMC().simpleString();
346: }
347:
348: /** Returns an IndentInfo containing the following information:
349: * - distance to the previous newline ( start of this line)
350: * - distance to the brace enclosing the beginning of the current line
351: * - distance to the beginning of the line containing that brace
352: */
353: public IndentInfo getIndentInformation() {
354: IndentInfo braceInfo = new IndentInfo();
355: //get distance to the previous newline (in braceInfo.distToNewline)
356: getRMC().getDistToPreviousNewline(braceInfo);
357: //get distance to the closing brace before that new line.
358: getRMB().getDistToEnclosingBrace(braceInfo);
359: //get distance to newline before the previous, just mentioned, brace.
360: getRMC().getDistToIndentNewline(braceInfo);
361: // get distance to the brace enclosing the current location
362: getRMB().getDistToEnclosingBraceCurrent(braceInfo);
363: // get distance to the beginning of that brace's line
364: getRMC().getDistToCurrentBraceNewline(braceInfo);
365: return braceInfo;
366: }
367:
368: /** Gets distance to end of line on the line previous. */
369: public int getDistToPreviousNewline(int relLoc) {
370: return getRMC().getDistToPreviousNewline(relLoc);
371: }
372:
373: public int getDistToNextNewline() {
374: return getRMC().getDistToNextNewline();
375: }
376:
377: /** Return all highlight status info for text between the current location and current location + length. This should
378: * collapse adjoining blocks with the same status into one.
379: * @param start The start location of the area for which we want the status. The reduced model is already at this
380: * position, but this value is needed to determine the absolute positions in HighlightStatus objects we return.
381: * @param length The length of the text segment for which status information must be generated.
382: */
383: public Vector<HighlightStatus> getHighlightStatus(final int start,
384: final int length) {
385: Vector<HighlightStatus> vec = new Vector<HighlightStatus>();
386:
387: int curState;
388: int curLocation;
389: int curLength;
390:
391: TokenList.Iterator cursor = getRMC()._cursor._copy();
392: // int ct = rmc._tokens.listenerCount();
393: curLocation = start;
394: // NOTE: old code threw an exception if cursor.atStart(); it used wrong value for curLength atEnd too
395: // curLength = ! cursor.atEnd() ? cursor.current().getSize() - rmc.getBlockOffset() : start + length;
396: // curState = ! cursor.atEnd() ? cursor.current().getHighlightState() : 0;
397: if (cursor.atEnd() || cursor.atStart()) { // cursor is not inside a reduced model token
398: curLength = length;
399: curState = 0;
400: } else {
401: curLength = cursor.current().getSize()
402: - getRMC().getBlockOffset();
403: curState = cursor.current().getHighlightState();
404: }
405:
406: while ((curLocation + curLength) < (start + length)) {
407: cursor.next();
408: //TODO: figure out why this function is iterating past the end of the collection
409: //when it gets called from the ColoringGlyphPainter after deleting the last character
410: if (cursor.atEnd())
411: break;
412: int nextState = cursor.current().getHighlightState();
413:
414: if (nextState == curState) {
415: // add on and keep building
416: curLength += cursor.current().getSize();
417: } else {
418: // add old one to the vector and start new one
419: vec.add(new HighlightStatus(curLocation, curLength,
420: curState));
421: curLocation += curLength; // new block starts after previous one
422: curLength = cursor.current().getSize();
423: curState = nextState;
424: }
425: }
426:
427: // Make sure this token length doesn't extend past start+length.
428: // This is because we guarantee that the returned vector only refers
429: // to chars on [start, start+length).
430: int requestEnd = start + length;
431: if ((curLocation + curLength) > requestEnd) {
432: curLength = requestEnd - curLocation;
433: }
434:
435: // Add the last one, which has not been added yet
436: vec.add(new HighlightStatus(curLocation, curLength, curState));
437:
438: cursor.dispose();
439:
440: return vec;
441: }
442: }
|