blob: 33aa27ddd63ae30b962e609c06fb0526a1118c3f [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2000, 2020 IBM Corporation and others.
*
* This program and the accompanying materials
* are made available under the terms of the Eclipse Public License 2.0
* which accompanies this distribution, and is available at
* https://www.eclipse.org/legal/epl-2.0/
*
* SPDX-License-Identifier: EPL-2.0
*
* Contributors:
* IBM Corporation - initial API and implementation
*******************************************************************************/
package org.eclipse.jdt.internal.ui.text;
import org.eclipse.jface.viewers.ISelection;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IDocumentExtension3;
import org.eclipse.jface.text.IDocumentPartitioner;
import org.eclipse.jface.text.ITypedRegion;
import org.eclipse.jface.text.TextUtilities;
import org.eclipse.jface.text.rules.ICharacterScanner;
import org.eclipse.jface.text.rules.IPartitionTokenScanner;
import org.eclipse.jface.text.rules.IToken;
import org.eclipse.jface.text.rules.Token;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.internal.corext.util.JavaModelUtil;
import org.eclipse.jdt.ui.text.IJavaPartitions;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.EditorUtility;
/**
* This scanner recognizes the JavaDoc comments, Java multi line comments, Java single line comments,
* Java strings and Java characters.
*/
public class FastJavaPartitionScanner implements IPartitionTokenScanner, IJavaPartitions {
// states
private static final int JAVA= 0;
private static final int SINGLE_LINE_COMMENT= 1;
private static final int MULTI_LINE_COMMENT= 2;
private static final int JAVADOC= 3;
private static final int CHARACTER= 4;
private static final int STRING= 5;
private static final int MULTI_LINE_STRING= 6;
// beginning of prefixes and postfixes
private static final int NONE= 0;
private static final int BACKSLASH= 1; // postfix for STRING and CHARACTER
private static final int SLASH= 2; // prefix for SINGLE_LINE or MULTI_LINE or JAVADOC
private static final int SLASH_STAR= 3; // prefix for MULTI_LINE_COMMENT or JAVADOC
private static final int SLASH_STAR_STAR= 4; // prefix for MULTI_LINE_COMMENT or JAVADOC
private static final int STAR= 5; // postfix for MULTI_LINE_COMMENT or JAVADOC
private static final int CARRIAGE_RETURN=6; // postfix for STRING, CHARACTER and SINGLE_LINE_COMMENT
private static final int TRIPLE_QUOTE= 9; // prefix for TextBlock.
/** The scanner. */
private final BufferedDocumentScanner fScanner= new BufferedDocumentScanner(1000); // faster implementation
private IDocument fCurrentDocument;
/** The offset of the last returned token. */
private int fTokenOffset;
/** The length of the last returned token. */
private int fTokenLength;
/** The state of the scanner. */
private int fState;
/** The last significant characters read. */
private int fLast;
/** The amount of characters already read on first call to nextToken(). */
private int fPrefixLength;
// emulate JavaPartitionScanner
private boolean fEmulate= false;
private int fJavaOffset;
private int fJavaLength;
private IJavaProject fJavaProject;
private final IToken[] fTokens= new IToken[] {
new Token(null),
new Token(JAVA_SINGLE_LINE_COMMENT),
new Token(JAVA_MULTI_LINE_COMMENT),
new Token(JAVA_DOC),
new Token(JAVA_CHARACTER),
new Token(JAVA_STRING),
new Token(JAVA_MULTI_LINE_STRING)
};
public FastJavaPartitionScanner(boolean emulate) {
fEmulate= emulate;
}
public FastJavaPartitionScanner() {
this(false);
}
public FastJavaPartitionScanner(IJavaProject javaProject) {
this(false);
fJavaProject= javaProject;
}
/*
* @see org.eclipse.jface.text.rules.ITokenScanner#nextToken()
*/
@Override
public IToken nextToken() {
// emulate JavaPartitionScanner
if (fEmulate) {
if (fJavaOffset != -1 && fTokenOffset + fTokenLength != fJavaOffset + fJavaLength) {
fTokenOffset += fTokenLength;
return fTokens[JAVA];
} else {
fJavaOffset= -1;
fJavaLength= 0;
}
}
fTokenOffset += fTokenLength;
fTokenLength= fPrefixLength;
while (true) {
final int ch= fScanner.read();
// characters
switch (ch) {
case ICharacterScanner.EOF:
if (fTokenLength > 0) {
fLast= NONE; // ignore last
return preFix(fState, JAVA, NONE, 0);
} else {
fLast= NONE;
fPrefixLength= 0;
return Token.EOF;
}
case '\r':
// emulate JavaPartitionScanner
if (!fEmulate && fLast != CARRIAGE_RETURN) {
fLast= CARRIAGE_RETURN;
fTokenLength++;
continue;
} else {
switch (fState) {
case SINGLE_LINE_COMMENT:
case CHARACTER:
case STRING:
if (fTokenLength > 0) {
IToken token= fTokens[fState];
// emulate JavaPartitionScanner
if (fEmulate) {
fTokenLength++;
fLast= NONE;
fPrefixLength= 0;
} else {
fLast= CARRIAGE_RETURN;
fPrefixLength= 1;
}
fState= JAVA;
return token;
} else {
consume();
continue;
}
default:
consume();
continue;
}
}
case '\n':
switch (fState) {
case SINGLE_LINE_COMMENT:
case CHARACTER:
case STRING:
// assert(fTokenLength > 0);
return postFix(fState);
default:
consume();
continue;
}
default:
if (!fEmulate && fLast == CARRIAGE_RETURN) {
switch (fState) {
case SINGLE_LINE_COMMENT:
case CHARACTER:
case STRING:
int last;
int newState;
switch (ch) {
case '/':
last= SLASH;
newState= JAVA;
break;
case '*':
last= STAR;
newState= JAVA;
break;
case '\'':
last= NONE;
newState= CHARACTER;
break;
case '"':
last= NONE;
newState= STRING;
break;
case '\r':
last= CARRIAGE_RETURN;
newState= JAVA;
break;
case '\\':
last= BACKSLASH;
newState= JAVA;
break;
default:
last= NONE;
newState= JAVA;
break;
}
fLast= NONE; // ignore fLast
return preFix(fState, newState, last, 1);
default:
break;
}
}
}
// states
switch (fState) {
case JAVA:
switch (ch) {
case '/':
if (fLast == SLASH) {
if (fTokenLength - getLastLength(fLast) > 0) {
return preFix(JAVA, SINGLE_LINE_COMMENT, NONE, 2);
} else {
preFix(JAVA, SINGLE_LINE_COMMENT, NONE, 2);
fTokenOffset += fTokenLength;
fTokenLength= fPrefixLength;
break;
}
} else {
fTokenLength++;
fLast= SLASH;
break;
}
case '*':
if (fLast == SLASH) {
if (fTokenLength - getLastLength(fLast) > 0)
return preFix(JAVA, MULTI_LINE_COMMENT, SLASH_STAR, 2);
else {
preFix(JAVA, MULTI_LINE_COMMENT, SLASH_STAR, 2);
fTokenOffset += fTokenLength;
fTokenLength= fPrefixLength;
break;
}
} else {
consume();
break;
}
case '\'':
fLast= NONE; // ignore fLast
if (fTokenLength > 0)
return preFix(JAVA, CHARACTER, NONE, 1);
else {
preFix(JAVA, CHARACTER, NONE, 1);
fTokenOffset += fTokenLength;
fTokenLength= fPrefixLength;
break;
}
case '"':
boolean isTextBlockBeginning= scanForTextBlockBeginning();
if (isTextBlockBeginning) {
if (fTokenLength - getLastLength(fLast) > 0)
return preFix(JAVA, MULTI_LINE_STRING, TRIPLE_QUOTE, 3);
else {
preFix(JAVA, MULTI_LINE_STRING, TRIPLE_QUOTE, 3);
fTokenOffset+= fTokenLength;
fTokenLength= fPrefixLength;
break;
}
}
else {
fLast= NONE; // ignore fLast
if (fTokenLength > 0)
return preFix(JAVA, STRING, NONE, 1);
else {
preFix(JAVA, STRING, NONE, 1);
fTokenOffset += fTokenLength;
fTokenLength= fPrefixLength;
break;
}
}
default:
consume();
break;
}
break;
case SINGLE_LINE_COMMENT:
consume();
break;
case JAVADOC:
switch (ch) {
case '/':
switch (fLast) {
case SLASH_STAR_STAR:
return postFix(MULTI_LINE_COMMENT);
case STAR:
return postFix(JAVADOC);
default:
consume();
break;
}
break;
case '*':
fTokenLength++;
fLast= STAR;
break;
default:
consume();
break;
}
break;
case MULTI_LINE_COMMENT:
switch (ch) {
case '*':
if (fLast == SLASH_STAR) {
fLast= SLASH_STAR_STAR;
fTokenLength++;
fState= JAVADOC;
} else {
fTokenLength++;
fLast= STAR;
}
break;
case '/':
if (fLast == STAR) {
return postFix(MULTI_LINE_COMMENT);
} else {
consume();
break;
}
default:
consume();
break;
}
break;
case STRING:
switch (ch) {
case '\\':
fLast= (fLast == BACKSLASH) ? NONE : BACKSLASH;
fTokenLength++;
if (scanForUnicodeSlash()) {
fTokenLength= fTokenLength + 5;
}
break;
case '\"':
if (fLast != BACKSLASH) {
return postFix(STRING);
} else {
consume();
break;
}
default:
consume();
break;
}
break;
case CHARACTER:
switch (ch) {
case '\\':
fLast= (fLast == BACKSLASH) ? NONE : BACKSLASH;
fTokenLength++;
if (scanForUnicodeSlash()) {
fTokenLength= fTokenLength + 5;
}
break;
case '\'':
if (fLast != BACKSLASH) {
return postFix(CHARACTER);
} else {
consume();
break;
}
default:
consume();
break;
}
break;
case MULTI_LINE_STRING:
switch (ch) {
case '\\':
fLast= (fLast == BACKSLASH) ? NONE : BACKSLASH;
fTokenLength++;
if (scanForUnicodeSlash()) {
fTokenLength= fTokenLength + 5;
}
break;
case '\"':
if (fLast == BACKSLASH || !scanForTextBlockClose()) {
consume();
break;
} else {
boolean considerEndQuotes= true;
try {
IDocumentPartitioner docPartitioner= fCurrentDocument.getDocumentPartitioner();
if (fCurrentDocument instanceof IDocumentExtension3) {
docPartitioner= ((IDocumentExtension3)fCurrentDocument).getDocumentPartitioner(IJavaPartitions.JAVA_PARTITIONING);
}
if (docPartitioner instanceof FastJavaPartitioner) {
FastJavaPartitioner fjPartitioner= (FastJavaPartitioner) docPartitioner;
if (!fjPartitioner.hasTextBlockSupportedValueChanged()) {
ITypedRegion originalPartition= TextUtilities.getPartition(fCurrentDocument, IJavaPartitions.JAVA_PARTITIONING, fTokenOffset, false);
ITypedRegion startingPartition= TextUtilities.getPartition(fCurrentDocument, IJavaPartitions.JAVA_PARTITIONING, fTokenOffset+ fTokenLength+2, false);
fjPartitioner.resetPositionCache();
if (!originalPartition.equals(startingPartition)) {
String startingType= startingPartition.getType();
if (IJavaPartitions.JAVA_MULTI_LINE_STRING.equals(startingType)) {
considerEndQuotes= false;
}
}
}
}
if (!considerEndQuotes) {
for (int i=0; i< 3; i++) {
fScanner.unread();
}
}
} catch (BadLocationException e) {
//do nothing
}
if (considerEndQuotes) {
fTokenLength= fTokenLength + 2;
} else {
fTokenLength= fTokenLength - 1;
}
return postFix(MULTI_LINE_STRING);
}
default:
consume();
break;
}
break;
}
}
}
private boolean scanForUnicodeSlash() {
int count= 0;
boolean isUnicodeSlash= false;
try {
int ch= fScanner.read();
count++;
if (ch == 'u') {
ch= fScanner.read();
count++;
if (ch == '0') {
ch= fScanner.read();
count++;
if (ch == '0') {
ch= fScanner.read();
count++;
if (ch == '5') {
ch= fScanner.read();
count++;
if (ch == 'C') {
isUnicodeSlash= true;
}
}
}
}
}
if (ch == ICharacterScanner.EOF) {
count--;
}
} catch (IndexOutOfBoundsException e) {
//let it return false;
} finally {
if (!isUnicodeSlash) {
for (int i= count; i > 0; i--) {
fScanner.unread();
}
}
}
return isUnicodeSlash;
}
private boolean scanForTextBlockBeginning() {
if (!isTextBlockSupported()) {
return false;
}
int count= 0;
boolean isTextBlockBeginning= false;
try {
// Don't change the position and current character unless we are certain
// to be dealing with a text block. For producing all errors like before
// in case of a valid """ but missing \r or \n, just return false and not
// throw any error.
count++;
int ch= fScanner.read();
if (ch == '\"') {
count++;
ch= fScanner.read();
if (ch == '\"') {
count++;
ch= fScanner.read();
while (Character.isWhitespace(ch)) {
switch (ch) {
case 10: /* \ u000a: LINE FEED */
case 13: /* \ u000d: CARRIAGE RETURN */
isTextBlockBeginning= true;
break;
case ICharacterScanner.EOF:
count--;
break;
default:
count++;
ch= fScanner.read();
break;
}
if (isTextBlockBeginning) {
break;
}
}
} else if (ch == ICharacterScanner.EOF) {
count--;
}
} else if (ch == ICharacterScanner.EOF) {
count--;
}
} catch (IndexOutOfBoundsException e) {
//let it return false;
} finally {
int ignore= 0;
if (isTextBlockBeginning) {
ignore= 2;
}
for (int i= count - ignore; i > 0; i--) {
fScanner.unread();
}
}
return isTextBlockBeginning;
}
private boolean scanForTextBlockClose() {
int count= 0;
boolean isTextBlockEnd= false;
try {
count++;
int ch= fScanner.read();
if (ch == '\"') {
count++;
ch= fScanner.read();
if (ch == '\"') {
isTextBlockEnd= true;
} else if (ch == ICharacterScanner.EOF) {
count--;
}
} else if (ch == ICharacterScanner.EOF) {
count--;
}
} catch (IndexOutOfBoundsException e) {
//let it return false;
} finally {
if (!isTextBlockEnd) {
for (int i= count; i > 0; i--) {
fScanner.unread();
}
}
}
return isTextBlockEnd;
}
private static final int getLastLength(int last) {
switch (last) {
default:
return -1;
case NONE:
return 0;
case CARRIAGE_RETURN:
case BACKSLASH:
case SLASH:
case STAR:
return 1;
case SLASH_STAR:
return 2;
case SLASH_STAR_STAR:
case TRIPLE_QUOTE:
return 3;
}
}
private final void consume() {
fTokenLength++;
fLast= NONE;
}
private final IToken postFix(int state) {
fTokenLength++;
fLast= NONE;
fState= JAVA;
fPrefixLength= 0;
return fTokens[state];
}
private final IToken preFix(int state, int newState, int last, int prefixLength) {
// emulate JavaPartitionScanner
if (fEmulate && state == JAVA && (fTokenLength - getLastLength(fLast) > 0)) {
fTokenLength -= getLastLength(fLast);
fJavaOffset= fTokenOffset;
fJavaLength= fTokenLength;
fTokenLength= 1;
fState= newState;
fPrefixLength= prefixLength;
fLast= last;
return fTokens[state];
} else {
fTokenLength -= getLastLength(fLast);
fLast= last;
fPrefixLength= prefixLength;
IToken token= fTokens[state];
fState= newState;
return token;
}
}
private static int getState(String contentType) {
if (contentType == null)
return JAVA;
switch (contentType) {
case JAVA_SINGLE_LINE_COMMENT:
return SINGLE_LINE_COMMENT;
case JAVA_MULTI_LINE_COMMENT:
return MULTI_LINE_COMMENT;
case JAVA_DOC:
return JAVADOC;
case JAVA_STRING:
return STRING;
case JAVA_CHARACTER:
return CHARACTER;
case JAVA_MULTI_LINE_STRING:
return MULTI_LINE_STRING;
default:
return JAVA;
}
}
/*
* @see IPartitionTokenScanner#setPartialRange(IDocument, int, int, String, int)
*/
@Override
public void setPartialRange(IDocument document, int offset, int length, String contentType, int partitionOffset) {
fCurrentDocument= document;
fScanner.setRange(document, offset, length);
fTokenOffset= partitionOffset;
fTokenLength= 0;
fPrefixLength= offset - partitionOffset;
fLast= NONE;
if (offset == partitionOffset) {
// restart at beginning of partition
fState= JAVA;
} else {
fState= getState(contentType);
}
// emulate JavaPartitionScanner
if (fEmulate) {
fJavaOffset= -1;
fJavaLength= 0;
}
}
/*
* @see ITokenScanner#setRange(IDocument, int, int)
*/
@Override
public void setRange(IDocument document, int offset, int length) {
fCurrentDocument= document;
fScanner.setRange(document, offset, length);
fTokenOffset= offset;
fTokenLength= 0;
fPrefixLength= 0;
fLast= NONE;
fState= JAVA;
// emulate JavaPartitionScanner
if (fEmulate) {
fJavaOffset= -1;
fJavaLength= 0;
}
}
/*
* @see ITokenScanner#getTokenLength()
*/
@Override
public int getTokenLength() {
return fTokenLength;
}
/*
* @see ITokenScanner#getTokenOffset()
*/
@Override
public int getTokenOffset() {
return fTokenOffset;
}
private void setJavaProject() {
if (fJavaProject == null) {
IWorkbenchPage page= null;
try {
page= JavaPlugin.getActivePage();
} catch (IllegalStateException e) {
//do nothing
}
if (page != null) {
IEditorPart part= page.getActiveEditor();
if (part != null) {
IEditorInput editorInput= part.getEditorInput();
if (editorInput != null) {
fJavaProject= EditorUtility.getJavaProject(editorInput);
}
}
if (fJavaProject == null) {
ISelection selection= page.getSelection();
if (selection instanceof IStructuredSelection && !selection.isEmpty()) {
Object obj= ((IStructuredSelection) selection).getFirstElement();
if (obj instanceof IJavaElement) {
fJavaProject= ((IJavaElement) obj).getJavaProject();
}
}
}
}
}
}
public boolean isTextBlockSupported() {
boolean isAllowed= false;
setJavaProject();
if (fJavaProject != null) {
isAllowed= JavaModelUtil.is15OrHigher(fJavaProject);
}
return isAllowed;
}
}