/******************************************************************************* * Copyright (c) 2000, 2008 IBM Corporation and others. * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * IBM Corporation - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.java.jdt.internal.corext.dom; import org.eclipse.che.ide.ext.java.jdt.JavaUIStatus; import org.eclipse.che.ide.ext.java.jdt.core.JavaCore; import org.eclipse.che.ide.ext.java.jdt.core.ToolFactory; import org.eclipse.che.ide.ext.java.jdt.core.compiler.IScanner; import org.eclipse.che.ide.ext.java.jdt.core.compiler.ITerminalSymbols; import org.eclipse.che.ide.ext.java.jdt.core.compiler.InvalidInputException; import org.eclipse.che.ide.ext.java.jdt.text.Document; import org.eclipse.che.ide.runtime.CoreException; import org.eclipse.che.ide.runtime.IStatus; import org.eclipse.che.ide.api.text.BadLocationException; import org.eclipse.che.ide.api.text.Region; /** Wraps a scanner and offers convenient methods for finding tokens */ public class TokenScanner { public static final int END_OF_FILE = 20001; public static final int LEXICAL_ERROR = 20002; public static final int DOCUMENT_ERROR = 20003; private IScanner fScanner; private Document fDocument; private int fEndPosition; /** * Creates a TokenScanner * * @param scanner * The scanner to be wrapped. The scanner has to support line information * if the comment position methods are used. */ public TokenScanner(IScanner scanner) { this(scanner, null); } /** * Creates a TokenScanner * * @param scanner * The scanner to be wrapped * @param document * The document used for line information if specified */ public TokenScanner(IScanner scanner, Document document) { fScanner = scanner; fEndPosition = fScanner.getSource().length - 1; fDocument = document; } /** * Creates a TokenScanner * * @param document * The textbuffer to create the scanner on * @param project * the current Java project */ public TokenScanner(Document document) { String sourceLevel = JavaCore.getOption(JavaCore.COMPILER_SOURCE); String complianceLevel = JavaCore.getOption(JavaCore.COMPILER_COMPLIANCE); fScanner = ToolFactory.createScanner(true, false, false, sourceLevel, complianceLevel); // no line info required fScanner.setSource(document.get().toCharArray()); fDocument = document; fEndPosition = fScanner.getSource().length - 1; } // /** // * Creates a TokenScanner // * @param typeRoot The type root to scan on // * @throws CoreException thrown if the buffer cannot be accessed // */ // public TokenScanner(ITypeRoot typeRoot) throws CoreException // { // IJavaProject project = typeRoot.getJavaProject(); // IBuffer buffer = typeRoot.getBuffer(); // if (buffer == null) // { // throw new CoreException(createError(DOCUMENT_ERROR, "Element has no source", null)); //$NON-NLS-1$ // } // String sourceLevel = project.getOption(JavaCore.COMPILER_SOURCE, true); // String complianceLevel = project.getOption(JavaCore.COMPILER_COMPLIANCE, true); // fScanner = ToolFactory.createScanner(true, false, true, sourceLevel, complianceLevel); // line info required // // fScanner.setSource(buffer.getCharacters()); // fDocument = null; // use scanner for line information // fEndPosition = fScanner.getSource().length - 1; // } /** * Returns the wrapped scanner * * @return IScanner */ public IScanner getScanner() { return fScanner; } /** * Sets the scanner offset to the given offset. * * @param offset * The offset to set */ public void setOffset(int offset) { fScanner.resetTo(offset, fEndPosition); } /** @return Returns the offset after the current token */ public int getCurrentEndOffset() { return fScanner.getCurrentTokenEndPosition() + 1; } /** @return Returns the start offset of the current token */ public int getCurrentStartOffset() { return fScanner.getCurrentTokenStartPosition(); } /** @return Returns the length of the current token */ public int getCurrentLength() { return getCurrentEndOffset() - getCurrentStartOffset(); } /** * Reads the next token. * * @param ignoreComments * If set, comments will be overread * @return Return the token id. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int readNext(boolean ignoreComments) throws CoreException { int curr = 0; do { try { curr = fScanner.getNextToken(); if (curr == ITerminalSymbols.TokenNameEOF) { throw new CoreException(createError(END_OF_FILE, "End Of File", null)); //$NON-NLS-1$ } } catch (InvalidInputException e) { throw new CoreException(createError(LEXICAL_ERROR, e.getMessage(), e)); } } while (ignoreComments && isComment(curr)); return curr; } /** * Reads the next token. * * @param ignoreComments * If set, comments will be overread. * @return Return the token id. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ private int readNextWithEOF(boolean ignoreComments) throws CoreException { int curr = 0; do { try { curr = fScanner.getNextToken(); } catch (InvalidInputException e) { throw new CoreException(createError(LEXICAL_ERROR, e.getMessage(), e)); } } while (ignoreComments && isComment(curr)); return curr; } /** * Reads the next token from the given offset. * * @param offset * The offset to start reading from. * @param ignoreComments * If set, comments will be overread. * @return Returns the token id. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int readNext(int offset, boolean ignoreComments) throws CoreException { setOffset(offset); return readNext(ignoreComments); } /** * Reads the next token from the given offset and returns the start offset of the token. * * @param offset * The offset to start reading from. * @param ignoreComments * If set, comments will be overread * @return Returns the start position of the next token. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getNextStartOffset(int offset, boolean ignoreComments) throws CoreException { readNext(offset, ignoreComments); return getCurrentStartOffset(); } /** * Reads the next token from the given offset and returns the offset after the token. * * @param offset * The offset to start reading from. * @param ignoreComments * If set, comments will be overread * @return Returns the end position of the next token. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getNextEndOffset(int offset, boolean ignoreComments) throws CoreException { readNext(offset, ignoreComments); return getCurrentEndOffset(); } /** * Reads until a token is reached. * * @param tok * The token to read to. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public void readToToken(int tok) throws CoreException { int curr = 0; do { curr = readNext(false); } while (curr != tok); } /** * Reads until a token is reached, starting from the given offset. * * @param tok * The token to read to. * @param offset * The offset to start reading from. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public void readToToken(int tok, int offset) throws CoreException { setOffset(offset); readToToken(tok); } /** * Reads from the given offset until a token is reached and returns the start offset of the token. * * @param token * The token to be found. * @param startOffset * The offset to start reading from. * @return Returns the start position of the found token. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getTokenStartOffset(int token, int startOffset) throws CoreException { readToToken(token, startOffset); return getCurrentStartOffset(); } /** * Reads from the given offset until a token is reached and returns the offset after the token. * * @param token * The token to be found. * @param startOffset * Offset to start reading from * @return Returns the end position of the found token. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getTokenEndOffset(int token, int startOffset) throws CoreException { readToToken(token, startOffset); return getCurrentEndOffset(); } /** * Reads from the given offset until a token is reached and returns the offset after the previous token. * * @param token * The token to be found. * @param startOffset * The offset to start scanning from. * @return Returns the end offset of the token previous to the given token. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getPreviousTokenEndOffset(int token, int startOffset) throws CoreException { setOffset(startOffset); int res = startOffset; int curr = readNext(false); while (curr != token) { res = getCurrentEndOffset(); curr = readNext(false); } return res; } /** * Evaluates the start offset of comments directly ahead of a token specified by its start offset * * @param lastPos * An offset to before the node start offset. Can be 0 but better is the end location of the previous node. * @param nodeStart * Start offset of the node to find the comments for. * @return Returns the start offset of comments directly ahead of a token. * @throws CoreException * Thrown when a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getTokenCommentStart(int lastPos, int nodeStart) throws CoreException { setOffset(lastPos); int prevEndPos = lastPos; int prevEndLine = prevEndPos > 0 ? getLineOfOffset(prevEndPos - 1) : 0; int nodeLine = getLineOfOffset(nodeStart); int res = -1; int curr = readNextWithEOF(false); int currStartPos = getCurrentStartOffset(); int currStartLine = getLineOfOffset(currStartPos); while (curr != ITerminalSymbols.TokenNameEOF && nodeStart > currStartPos) { if (TokenScanner.isComment(curr)) { int linesDifference = currStartLine - prevEndLine; if ((linesDifference > 1) || (res == -1 && (linesDifference != 0 || nodeLine == currStartLine))) { res = currStartPos; // begin new } } else { res = -1; } if (curr == ITerminalSymbols.TokenNameCOMMENT_LINE) { prevEndLine = currStartLine; } else { prevEndLine = getLineOfOffset(getCurrentEndOffset() - 1); } curr = readNextWithEOF(false); currStartPos = getCurrentStartOffset(); currStartLine = getLineOfOffset(currStartPos); } if (res == -1 || curr == ITerminalSymbols.TokenNameEOF) { return nodeStart; } if (currStartLine - prevEndLine > 1) { return nodeStart; } return res; } /** * Looks for comments after a node and returns the end position of the comment still belonging to the node. * * @param nodeEnd * The end position of the node * @param nextTokenStart * The start positoion of the next node. Optional, can be -1 * the line information shoould be taken from the scanner object * @return Returns returns the end position of the comment still belonging to the node. * @throws CoreException * Thrown when the end of the file has been reached (code END_OF_FILE) * or a lexical error was detected while scanning (code LEXICAL_ERROR) */ public int getTokenCommentEnd(int nodeEnd, int nextTokenStart) throws CoreException { // assign comments to the previous comments as long they are all on the same line as the // node end position or if they are on the next line but there is a separation from the next // node // } //aa // // aa // // // bb // public void b... // // } /* cc */ /* // cc/* // /*dd*/ // public void d... int prevEndLine = getLineOfOffset(nodeEnd - 1); int prevEndPos = nodeEnd; int res = nodeEnd; boolean sameLineComment = true; setOffset(nodeEnd); int curr = readNextWithEOF(false); while (curr == ITerminalSymbols.TokenNameCOMMENT_LINE || curr == ITerminalSymbols.TokenNameCOMMENT_BLOCK) { int currStartLine = getLineOfOffset(getCurrentStartOffset()); int linesDifference = currStartLine - prevEndLine; if (linesDifference > 1) { return prevEndPos; // separated comments } if (curr == ITerminalSymbols.TokenNameCOMMENT_LINE) { prevEndPos = getLineEnd(currStartLine); prevEndLine = currStartLine; } else { prevEndPos = getCurrentEndOffset(); prevEndLine = getLineOfOffset(prevEndPos - 1); } if (sameLineComment) { if (linesDifference == 0) { res = prevEndPos; } else { sameLineComment = false; } } curr = readNextWithEOF(false); } if (curr == ITerminalSymbols.TokenNameEOF) { return prevEndPos; } int currStartLine = getLineOfOffset(getCurrentStartOffset()); int linesDifference = currStartLine - prevEndLine; if (linesDifference > 1) { return prevEndPos; // separated comments } return res; } public int getLineOfOffset(int offset) throws CoreException { if (fDocument != null) { try { return fDocument.getLineOfOffset(offset); } catch (BadLocationException e) { String message = "Illegal offset: " + offset; //$NON-NLS-1$ throw new CoreException(createError(DOCUMENT_ERROR, message, e)); } } return getScanner().getLineNumber(offset); } public int getLineEnd(int line) throws CoreException { if (fDocument != null) { try { Region region = fDocument.getLineInformation(line); return region.getOffset() + region.getLength(); } catch (BadLocationException e) { String message = "Illegal line: " + line; //$NON-NLS-1$ throw new CoreException(createError(DOCUMENT_ERROR, message, e)); } } return getScanner().getLineEnd(line); } public static boolean isComment(int token) { return token == ITerminalSymbols.TokenNameCOMMENT_BLOCK || token == ITerminalSymbols.TokenNameCOMMENT_JAVADOC || token == ITerminalSymbols.TokenNameCOMMENT_LINE; } public static boolean isModifier(int token) { switch (token) { case ITerminalSymbols.TokenNamepublic: case ITerminalSymbols.TokenNameprotected: case ITerminalSymbols.TokenNameprivate: case ITerminalSymbols.TokenNamestatic: case ITerminalSymbols.TokenNamefinal: case ITerminalSymbols.TokenNameabstract: case ITerminalSymbols.TokenNamenative: case ITerminalSymbols.TokenNamevolatile: case ITerminalSymbols.TokenNamestrictfp: case ITerminalSymbols.TokenNametransient: case ITerminalSymbols.TokenNamesynchronized: return true; default: return false; } } private IStatus createError(int code, String message, Throwable e) { return JavaUIStatus.createError(code, message, e); } }