001: /*******************************************************************************
002: * Copyright (c) 2000, 2006 IBM Corporation and others.
003: * All rights reserved. This program and the accompanying materials
004: * are made available under the terms of the Eclipse Public License v1.0
005: * which accompanies this distribution, and is available at
006: * http://www.eclipse.org/legal/epl-v10.html
007: *
008: * Contributors:
009: * IBM Corporation - initial API and implementation
010: *******************************************************************************/package org.eclipse.jdt.internal.corext.dom;
011:
012: import org.eclipse.core.runtime.CoreException;
013: import org.eclipse.core.runtime.IStatus;
014:
015: import org.eclipse.jface.text.BadLocationException;
016: import org.eclipse.jface.text.IDocument;
017: import org.eclipse.jface.text.IRegion;
018:
019: import org.eclipse.jdt.core.ICompilationUnit;
020: import org.eclipse.jdt.core.IJavaProject;
021: import org.eclipse.jdt.core.JavaCore;
022: import org.eclipse.jdt.core.JavaModelException;
023: import org.eclipse.jdt.core.ToolFactory;
024: import org.eclipse.jdt.core.compiler.IScanner;
025: import org.eclipse.jdt.core.compiler.ITerminalSymbols;
026: import org.eclipse.jdt.core.compiler.InvalidInputException;
027:
028: import org.eclipse.jdt.internal.ui.JavaUIStatus;
029:
030: /**
031: * Wraps a scanner and offers convenient methods for finding tokens
032: */
033: public class TokenScanner {
034:
035: public static final int END_OF_FILE = 20001;
036: public static final int LEXICAL_ERROR = 20002;
037: public static final int DOCUMENT_ERROR = 20003;
038:
039: private IScanner fScanner;
040: private IDocument fDocument;
041: private int fEndPosition;
042:
043: /**
044: * Creates a TokenScanner
045: * @param scanner The scanner to be wrapped. The scanner has to support line information
046: * if the comment position methods are used.
047: */
048: public TokenScanner(IScanner scanner) {
049: this (scanner, null);
050: }
051:
052: /**
053: * Creates a TokenScanner
054: * @param scanner The scanner to be wrapped
055: * @param document The document used for line information if specified
056: */
057: public TokenScanner(IScanner scanner, IDocument document) {
058: fScanner = scanner;
059: fEndPosition = fScanner.getSource().length - 1;
060: fDocument = document;
061: }
062:
063: /**
064: * Creates a TokenScanner
065: * @param document The textbuffer to create the scanner on
066: */
067: public TokenScanner(IDocument document, IJavaProject project) {
068: String sourceLevel = project.getOption(
069: JavaCore.COMPILER_SOURCE, true);
070: String complianceLevel = project.getOption(
071: JavaCore.COMPILER_COMPLIANCE, true);
072: fScanner = ToolFactory.createScanner(true, false, false,
073: sourceLevel, complianceLevel); // no line info required
074: fScanner.setSource(document.get().toCharArray());
075: fDocument = document;
076: fEndPosition = fScanner.getSource().length - 1;
077: }
078:
079: /**
080: * Creates a TokenScanner
081: * @param cu The compliation unit to can on
082: * @throws JavaModelException thorwn if the buffer cannot be accessed
083: */
084: public TokenScanner(ICompilationUnit cu) throws JavaModelException {
085: IJavaProject project = cu.getJavaProject();
086: String sourceLevel = project.getOption(
087: JavaCore.COMPILER_SOURCE, true);
088: String complianceLevel = project.getOption(
089: JavaCore.COMPILER_COMPLIANCE, true);
090: fScanner = ToolFactory.createScanner(true, false, true,
091: sourceLevel, complianceLevel); // line info required
092: fScanner.setSource(cu.getBuffer().getCharacters());
093: fDocument = null; // use scanner for line information
094: fEndPosition = fScanner.getSource().length - 1;
095: }
096:
097: /**
098: * Returns the wrapped scanner
099: * @return IScanner
100: */
101: public IScanner getScanner() {
102: return fScanner;
103: }
104:
105: /**
106: * Sets the scanner offset to the given offset.
107: * @param offset The offset to set
108: */
109: public void setOffset(int offset) {
110: fScanner.resetTo(offset, fEndPosition);
111: }
112:
113: /**
114: * @return Returns the offset after the current token
115: */
116: public int getCurrentEndOffset() {
117: return fScanner.getCurrentTokenEndPosition() + 1;
118: }
119:
120: /**
121: * @return Returns the start offset of the current token
122: */
123: public int getCurrentStartOffset() {
124: return fScanner.getCurrentTokenStartPosition();
125: }
126:
127: /**
128: * @return Returns the length of the current token
129: */
130: public int getCurrentLength() {
131: return getCurrentEndOffset() - getCurrentStartOffset();
132: }
133:
134: /**
135: * Reads the next token.
136: * @param ignoreComments If set, comments will be overread
137: * @return Return the token id.
138: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
139: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
140: */
141: public int readNext(boolean ignoreComments) throws CoreException {
142: int curr = 0;
143: do {
144: try {
145: curr = fScanner.getNextToken();
146: if (curr == ITerminalSymbols.TokenNameEOF) {
147: throw new CoreException(createError(END_OF_FILE,
148: "End Of File", null)); //$NON-NLS-1$
149: }
150: } catch (InvalidInputException e) {
151: throw new CoreException(createError(LEXICAL_ERROR, e
152: .getMessage(), e));
153: }
154: } while (ignoreComments && isComment(curr));
155: return curr;
156: }
157:
158: /**
159: * Reads the next token.
160: * @param ignoreComments If set, comments will be overread.
161: * @return Return the token id.
162: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
163: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
164: */
165: private int readNextWithEOF(boolean ignoreComments)
166: throws CoreException {
167: int curr = 0;
168: do {
169: try {
170: curr = fScanner.getNextToken();
171: } catch (InvalidInputException e) {
172: throw new CoreException(createError(LEXICAL_ERROR, e
173: .getMessage(), e));
174: }
175: } while (ignoreComments && isComment(curr));
176: return curr;
177: }
178:
179: /**
180: * Reads the next token from the given offset.
181: * @param offset The offset to start reading from.
182: * @param ignoreComments If set, comments will be overread.
183: * @return Returns the token id.
184: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
185: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
186: */
187: public int readNext(int offset, boolean ignoreComments)
188: throws CoreException {
189: setOffset(offset);
190: return readNext(ignoreComments);
191: }
192:
193: /**
194: * Reads the next token from the given offset and returns the start offset of the token.
195: * @param offset The offset to start reading from.
196: * @param ignoreComments If set, comments will be overread
197: * @return Returns the start position of the next token.
198: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
199: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
200: */
201: public int getNextStartOffset(int offset, boolean ignoreComments)
202: throws CoreException {
203: readNext(offset, ignoreComments);
204: return getCurrentStartOffset();
205: }
206:
207: /**
208: * Reads the next token from the given offset and returns the offset after the token.
209: * @param offset The offset to start reading from.
210: * @param ignoreComments If set, comments will be overread
211: * @return Returns the start position of the next token.
212: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
213: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
214: */
215: public int getNextEndOffset(int offset, boolean ignoreComments)
216: throws CoreException {
217: readNext(offset, ignoreComments);
218: return getCurrentEndOffset();
219: }
220:
221: /**
222: * Reads until a token is reached.
223: * @param tok The token to read to.
224: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
225: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
226: */
227: public void readToToken(int tok) throws CoreException {
228: int curr = 0;
229: do {
230: curr = readNext(false);
231: } while (curr != tok);
232: }
233:
234: /**
235: * Reads until a token is reached, starting from the given offset.
236: * @param tok The token to read to.
237: * @param offset The offset to start reading from.
238: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
239: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
240: */
241: public void readToToken(int tok, int offset) throws CoreException {
242: setOffset(offset);
243: readToToken(tok);
244: }
245:
246: /**
247: * Reads from the given offset until a token is reached and returns the start offset of the token.
248: * @param token The token to be found.
249: * @param startOffset The offset to start reading from.
250: * @return Returns the start position of the found token.
251: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
252: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
253: */
254: public int getTokenStartOffset(int token, int startOffset)
255: throws CoreException {
256: readToToken(token, startOffset);
257: return getCurrentStartOffset();
258: }
259:
260: /**
261: * Reads from the given offset until a token is reached and returns the offset after the token.
262: * @param token The token to be found.
263: * @param startOffset Offset to start reading from
264: * @return Returns the end position of the found token.
265: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
266: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
267: */
268: public int getTokenEndOffset(int token, int startOffset)
269: throws CoreException {
270: readToToken(token, startOffset);
271: return getCurrentEndOffset();
272: }
273:
274: /**
275: * Reads from the given offset until a token is reached and returns the offset after the previous token.
276: * @param token The token to be found.
277: * @param startOffset The offset to start scanning from.
278: * @return Returns the end offset of the token previous to the given token.
279: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
280: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
281: */
282: public int getPreviousTokenEndOffset(int token, int startOffset)
283: throws CoreException {
284: setOffset(startOffset);
285: int res = startOffset;
286: int curr = readNext(false);
287: while (curr != token) {
288: res = getCurrentEndOffset();
289: curr = readNext(false);
290: }
291: return res;
292: }
293:
294: /**
295: * Evaluates the start offset of comments directly ahead of a token specified by its start offset
296: *
297: * @param lastPos An offset to before the node start offset. Can be 0 but better is the end location of the previous node.
298: * @param nodeStart Start offset of the node to find the comments for.
299: * @return Returns the start offset of comments directly ahead of a token.
300: * @exception CoreException Thrown when a lexical error was detected while scanning (code LEXICAL_ERROR)
301: */
302: public int getTokenCommentStart(int lastPos, int nodeStart)
303: throws CoreException {
304: setOffset(lastPos);
305:
306: int prevEndPos = lastPos;
307: int prevEndLine = prevEndPos > 0 ? getLineOfOffset(prevEndPos - 1)
308: : 0;
309: int nodeLine = getLineOfOffset(nodeStart);
310:
311: int res = -1;
312:
313: int curr = readNextWithEOF(false);
314: int currStartPos = getCurrentStartOffset();
315: int currStartLine = getLineOfOffset(currStartPos);
316: while (curr != ITerminalSymbols.TokenNameEOF
317: && nodeStart > currStartPos) {
318: if (TokenScanner.isComment(curr)) {
319: int linesDifference = currStartLine - prevEndLine;
320: if ((linesDifference > 1)
321: || (res == -1 && (linesDifference != 0 || nodeLine == currStartLine))) {
322: res = currStartPos; // begin new
323: }
324: } else {
325: res = -1;
326: }
327:
328: if (curr == ITerminalSymbols.TokenNameCOMMENT_LINE) {
329: prevEndLine = currStartLine;
330: } else {
331: prevEndLine = getLineOfOffset(getCurrentEndOffset() - 1);
332: }
333: curr = readNextWithEOF(false);
334: currStartPos = getCurrentStartOffset();
335: currStartLine = getLineOfOffset(currStartPos);
336: }
337: if (res == -1 || curr == ITerminalSymbols.TokenNameEOF) {
338: return nodeStart;
339: }
340: if (currStartLine - prevEndLine > 1) {
341: return nodeStart;
342: }
343: return res;
344: }
345:
346: /**
347: * Looks for comments after a node and returns the end position of the comment still belonging to the node.
348: * @param nodeEnd The end position of the node
349: * @param nextTokenStart The start positoion of the next node. Optional, can be -1
350: * the line information shoould be taken from the scanner object
351: * @return Returns returns the end position of the comment still belonging to the node.
352: * @exception CoreException Thrown when the end of the file has been reached (code END_OF_FILE)
353: * or a lexical error was detected while scanning (code LEXICAL_ERROR)
354: */
355: public int getTokenCommentEnd(int nodeEnd, int nextTokenStart)
356: throws CoreException {
357: // assign comments to the previous comments as long they are all on the same line as the
358: // node end position or if they are on the next line but there is a separation from the next
359: // node
360: // } //aa
361: // // aa
362: //
363: // // bb
364: // public void b...
365: //
366: // } /* cc */ /*
367: // cc/*
368: // /*dd*/
369: // public void d...
370:
371: int prevEndLine = getLineOfOffset(nodeEnd - 1);
372: int prevEndPos = nodeEnd;
373: int res = nodeEnd;
374: boolean sameLineComment = true;
375:
376: setOffset(nodeEnd);
377:
378: int curr = readNextWithEOF(false);
379: while (curr == ITerminalSymbols.TokenNameCOMMENT_LINE
380: || curr == ITerminalSymbols.TokenNameCOMMENT_BLOCK) {
381: int currStartLine = getLineOfOffset(getCurrentStartOffset());
382: int linesDifference = currStartLine - prevEndLine;
383:
384: if (linesDifference > 1) {
385: return prevEndPos; // separated comments
386: }
387:
388: if (curr == ITerminalSymbols.TokenNameCOMMENT_LINE) {
389: prevEndPos = getLineEnd(currStartLine);
390: prevEndLine = currStartLine;
391: } else {
392: prevEndPos = getCurrentEndOffset();
393: prevEndLine = getLineOfOffset(prevEndPos - 1);
394: }
395: if (sameLineComment) {
396: if (linesDifference == 0) {
397: res = prevEndPos;
398: } else {
399: sameLineComment = false;
400: }
401: }
402: curr = readNextWithEOF(false);
403: }
404: if (curr == ITerminalSymbols.TokenNameEOF) {
405: return prevEndPos;
406: }
407: int currStartLine = getLineOfOffset(getCurrentStartOffset());
408: int linesDifference = currStartLine - prevEndLine;
409: if (linesDifference > 1) {
410: return prevEndPos; // separated comments
411: }
412: return res;
413: }
414:
415: public int getLineOfOffset(int offset) throws CoreException {
416: if (fDocument != null) {
417: try {
418: return fDocument.getLineOfOffset(offset);
419: } catch (BadLocationException e) {
420: String message = "Illegal offset: " + offset; //$NON-NLS-1$
421: throw new CoreException(createError(DOCUMENT_ERROR,
422: message, e));
423: }
424: }
425: return getScanner().getLineNumber(offset);
426: }
427:
428: public int getLineEnd(int line) throws CoreException {
429: if (fDocument != null) {
430: try {
431: IRegion region = fDocument.getLineInformation(line);
432: return region.getOffset() + region.getLength();
433: } catch (BadLocationException e) {
434: String message = "Illegal line: " + line; //$NON-NLS-1$
435: throw new CoreException(createError(DOCUMENT_ERROR,
436: message, e));
437: }
438: }
439: return getScanner().getLineEnd(line);
440: }
441:
442: public static boolean isComment(int token) {
443: return token == ITerminalSymbols.TokenNameCOMMENT_BLOCK
444: || token == ITerminalSymbols.TokenNameCOMMENT_JAVADOC
445: || token == ITerminalSymbols.TokenNameCOMMENT_LINE;
446: }
447:
448: public static boolean isModifier(int token) {
449: switch (token) {
450: case ITerminalSymbols.TokenNamepublic:
451: case ITerminalSymbols.TokenNameprotected:
452: case ITerminalSymbols.TokenNameprivate:
453: case ITerminalSymbols.TokenNamestatic:
454: case ITerminalSymbols.TokenNamefinal:
455: case ITerminalSymbols.TokenNameabstract:
456: case ITerminalSymbols.TokenNamenative:
457: case ITerminalSymbols.TokenNamevolatile:
458: case ITerminalSymbols.TokenNamestrictfp:
459: case ITerminalSymbols.TokenNametransient:
460: case ITerminalSymbols.TokenNamesynchronized:
461: return true;
462: default:
463: return false;
464: }
465: }
466:
467: private IStatus createError(int code, String message, Throwable e) {
468: return JavaUIStatus.createError(DOCUMENT_ERROR, message, e);
469: // return new Status(IStatus.ERROR, JavaCore.PLUGIN_ID, code, message, throwable);
470: }
471:
472: }
|