blob: e741427c64517d8800b8f1ff399ad3e65adba986 [file] [log] [blame]
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.netbeans.modules.java.editor.base.semantic;
import com.sun.source.tree.AnnotatedTypeTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.BreakTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.ContinueTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.LabeledStatementTree;
import com.sun.source.tree.MemberReferenceTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.VariableTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import java.util.ArrayList;
import java.util.Collections;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.logging.Level;
import javax.swing.text.BadLocationException;
import javax.swing.text.Document;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.logging.Logger;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.Modifier;
import javax.lang.model.element.Name;
import javax.tools.Diagnostic;
import com.sun.source.tree.ModifiersTree;
import org.netbeans.api.java.lexer.JavaTokenId;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.api.lexer.TokenSequence;
import org.netbeans.api.java.source.CompilationInfo;
/**
*
* @author Jan Lahoda
*/
public class Utilities {
private static final Logger LOG = Logger.getLogger(Utilities.class.getName());
@Deprecated
private static final boolean DEBUG = false;
/** Creates a new instance of Utilities */
public Utilities() {
}
private static Token<JavaTokenId> findTokenWithText(CompilationInfo info, String text, int start, int end) {
TokenHierarchy<?> th = info.getTokenHierarchy();
TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language()).subSequence(start, end);
while (ts.moveNext()) {
Token<JavaTokenId> t = ts.token();
if (t.id() == JavaTokenId.IDENTIFIER) {
boolean nameMatches;
if (!(nameMatches = text.equals(info.getTreeUtilities().decodeIdentifier(t.text()).toString()))) {
ExpressionTree expr = info.getTreeUtilities().parseExpression(t.text().toString(), new SourcePositions[1]);
nameMatches = expr.getKind() == Kind.IDENTIFIER && text.contentEquals(((IdentifierTree) expr).getName());
}
if (nameMatches) {
return t;
}
}
}
return null;
}
private static Tree normalizeLastLeftTree(Tree lastLeft) {
while (lastLeft != null && lastLeft.getKind() == Kind.ARRAY_TYPE) {
lastLeft = ((ArrayTypeTree) lastLeft).getType();
}
return lastLeft;
}
private static Token<JavaTokenId> findIdentifierSpanImpl(CompilationInfo info, Tree decl, Tree lastLeft, List<? extends Tree> firstRight, String name, CompilationUnitTree cu, SourcePositions positions) {
int declStart = (int) positions.getStartPosition(cu, decl);
lastLeft = normalizeLastLeftTree(lastLeft);
int start = lastLeft != null ? (int)positions.getEndPosition(cu, lastLeft) : declStart;
if (start == (-1)) {
start = declStart;
if (start == (-1)) {
return null;
}
}
int end = (int)positions.getEndPosition(cu, decl);
for (Tree t : firstRight) {
if (t == null)
continue;
int proposedEnd = (int)positions.getStartPosition(cu, t);
if (proposedEnd != (-1) && proposedEnd < end)
end = proposedEnd;
}
if (end == (-1)) {
return null;
}
if (start > end) {
//may happend in case:
//public static String s() [] {}
//(meaning: method returning array of Strings)
//use a conservative start value:
start = (int) positions.getStartPosition(cu, decl);
}
return findTokenWithText(info, name, start, end);
}
private static Token<JavaTokenId> findIdentifierSpanImpl(CompilationInfo info, MemberSelectTree tree, CompilationUnitTree cu, SourcePositions positions) {
int start = (int)positions.getStartPosition(cu, tree);
int endPosition = (int)positions.getEndPosition(cu, tree);
if (start == (-1) || endPosition == (-1))
return null;
String member = tree.getIdentifier().toString();
TokenHierarchy<?> th = info.getTokenHierarchy();
TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language());
if (ts.move(endPosition) == Integer.MAX_VALUE) {
return null;
}
if (ts.moveNext()) {
while (ts.offset() >= start) {
Token<JavaTokenId> t = ts.token();
if (t.id() == JavaTokenId.IDENTIFIER && member.equals(info.getTreeUtilities().decodeIdentifier(t.text()).toString())) {
return t;
}
if (!ts.movePrevious())
break;
}
}
return null;
}
private static Token<JavaTokenId> findIdentifierSpanImpl(CompilationInfo info, MemberReferenceTree tree, CompilationUnitTree cu, SourcePositions positions) {
int start = (int)positions.getStartPosition(cu, tree);
int endPosition = (int)positions.getEndPosition(cu, tree);
if (start == (-1) || endPosition == (-1))
return null;
String member = tree.getName().toString();
TokenHierarchy<?> th = info.getTokenHierarchy();
TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language());
if (ts.move(endPosition) == Integer.MAX_VALUE) {
return null;
}
if (ts.moveNext()) {
while (ts.offset() >= start) {
Token<JavaTokenId> t = ts.token();
if (t.id() == JavaTokenId.IDENTIFIER && member.equals(info.getTreeUtilities().decodeIdentifier(t.text()).toString())) {
return t;
}
if (!ts.movePrevious())
break;
}
}
return null;
}
private static Token<JavaTokenId> findIdentifierSpanImpl(CompilationInfo info, IdentifierTree tree, CompilationUnitTree cu, SourcePositions positions) {
int start = (int)positions.getStartPosition(cu, tree);
int endPosition = (int)positions.getEndPosition(cu, tree);
if (start == (-1) || endPosition == (-1))
return null;
TokenHierarchy<?> th = info.getTokenHierarchy();
TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language());
if (ts.move(start) == Integer.MAX_VALUE) {
return null;
}
if (ts.moveNext()) {
if (ts.offset() >= start) {
Token<JavaTokenId> t = ts.token();
return t;
}
}
return null;
}
private static Token<JavaTokenId> findIdentifierSpanImplForBindingPattern(CompilationInfo info, Tree tree, CompilationUnitTree cu, SourcePositions positions) {
int start = (int)positions.getStartPosition(cu, tree);
int endPosition = (int)positions.getEndPosition(cu, tree);
if (start == (-1) || endPosition == (-1))
return null;
String member = TreeShims.getBinding(tree).toString();
TokenHierarchy<?> th = info.getTokenHierarchy();
TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language());
if (ts.move(endPosition) == Integer.MAX_VALUE) {
return null;
}
if (ts.moveNext()) {
while (ts.offset() >= start) {
Token<JavaTokenId> t = ts.token();
if (t.id() == JavaTokenId.IDENTIFIER && member.equals(info.getTreeUtilities().decodeIdentifier(t.text()).toString())) {
return t;
}
if (!ts.movePrevious())
break;
}
}
return null;
}
private static final Map<Class, List<Kind>> class2Kind;
static {
class2Kind = new HashMap<Class, List<Kind>>();
for (Kind k : Kind.values()) {
Class c = k.asInterface();
List<Kind> kinds = class2Kind.get(c);
if (kinds == null) {
class2Kind.put(c, kinds = new ArrayList<Kind>());
}
kinds.add(k);
}
}
private static Token<JavaTokenId> findIdentifierSpanImpl(CompilationInfo info, TreePath decl) {
if (info.getTreeUtilities().isSynthetic(decl))
return null;
Tree leaf = decl.getLeaf();
if (class2Kind.get(MethodTree.class).contains(leaf.getKind())) {
MethodTree method = (MethodTree) leaf;
List<Tree> rightTrees = new ArrayList<Tree>();
rightTrees.addAll(method.getParameters());
rightTrees.addAll(method.getThrows());
rightTrees.add(method.getBody());
Name name = method.getName();
if (method.getReturnType() == null)
name = ((ClassTree) decl.getParentPath().getLeaf()).getSimpleName();
return findIdentifierSpanImpl(info, leaf, method.getReturnType(), rightTrees, name.toString(), info.getCompilationUnit(), info.getTrees().getSourcePositions());
}
if (class2Kind.get(VariableTree.class).contains(leaf.getKind())) {
VariableTree var = (VariableTree) leaf;
// see #240912 - lambda implicit-typed parameter has synthetic type, shouldn't be searched.
boolean typeSynthetic = var.getType() == null || info.getTreeUtilities().isSynthetic(new TreePath(decl, var.getType()));
return findIdentifierSpanImpl(info, leaf,
typeSynthetic ? null : var.getType(),
Collections.singletonList(var.getInitializer()), var.getName().toString(), info.getCompilationUnit(), info.getTrees().getSourcePositions());
}
if (class2Kind.get(MemberSelectTree.class).contains(leaf.getKind())) {
return findIdentifierSpanImpl(info, (MemberSelectTree) leaf, info.getCompilationUnit(), info.getTrees().getSourcePositions());
}
if (class2Kind.get(MemberReferenceTree.class).contains(leaf.getKind())) {
return findIdentifierSpanImpl(info, (MemberReferenceTree) leaf, info.getCompilationUnit(), info.getTrees().getSourcePositions());
}
if (class2Kind.get(ClassTree.class).contains(leaf.getKind())) {
String name = ((ClassTree) leaf).getSimpleName().toString();
if (name.length() == 0)
return null;
SourcePositions positions = info.getTrees().getSourcePositions();
CompilationUnitTree cu = info.getCompilationUnit();
ModifiersTree mods = ((ClassTree) leaf).getModifiers();
int start = mods != null ? (int)positions.getEndPosition(cu, mods) : -1;
if (start == (-1))
start = (int)positions.getStartPosition(cu, leaf);
int end = (int)positions.getEndPosition(cu, leaf);
if (start == (-1) || end == (-1)) {
return null;
}
return findTokenWithText(info, name, start, end);
}
if (class2Kind.get(IdentifierTree.class).contains(leaf.getKind())) {
return findIdentifierSpanImpl(info, (IdentifierTree) leaf, info.getCompilationUnit(), info.getTrees().getSourcePositions());
}
if (class2Kind.get(ParameterizedTypeTree.class).contains(leaf.getKind())) {
return findIdentifierSpanImpl(info, new TreePath(decl, ((ParameterizedTypeTree) leaf).getType()));
}
if (class2Kind.get(AnnotatedTypeTree.class).contains(leaf.getKind())) {
return findIdentifierSpanImpl(info, new TreePath(decl, ((AnnotatedTypeTree) leaf).getUnderlyingType()));
}
if (class2Kind.get(BreakTree.class).contains(leaf.getKind())) {
Name name = ((BreakTree) leaf).getLabel();
if (name == null || name.length() == 0)
return null;
SourcePositions positions = info.getTrees().getSourcePositions();
CompilationUnitTree cu = info.getCompilationUnit();
int start = (int)positions.getStartPosition(cu, leaf);
int end = (int)positions.getEndPosition(cu, leaf);
if (start == (-1) || end == (-1)) {
return null;
}
return findTokenWithText(info, name.toString(), start, end);
}
if (class2Kind.get(ContinueTree.class).contains(leaf.getKind())) {
Name name = ((ContinueTree) leaf).getLabel();
if (name == null || name.length() == 0)
return null;
SourcePositions positions = info.getTrees().getSourcePositions();
CompilationUnitTree cu = info.getCompilationUnit();
int start = (int)positions.getStartPosition(cu, leaf);
int end = (int)positions.getEndPosition(cu, leaf);
if (start == (-1) || end == (-1)) {
return null;
}
return findTokenWithText(info, name.toString(), start, end);
}
if (class2Kind.get(LabeledStatementTree.class).contains(leaf.getKind())) {
Name name = ((LabeledStatementTree) leaf).getLabel();
if (name == null || name.length() == 0)
return null;
SourcePositions positions = info.getTrees().getSourcePositions();
CompilationUnitTree cu = info.getCompilationUnit();
int start = (int)positions.getStartPosition(cu, leaf);
int end = (int)positions.getStartPosition(cu, ((LabeledStatementTree) leaf).getStatement());
if (start == (-1) || end == (-1)) {
return null;
}
return findTokenWithText(info, name.toString(), start, end);
}
if (class2Kind.get(InstanceOfTree.class).contains(leaf.getKind())) {
Tree pattern = TreeShims.getPattern((InstanceOfTree) leaf);
if (pattern == null || !"BINDING_PATTERN".equals(pattern.getKind().name()))
return null;
Name name = TreeShims.getBinding(pattern);
if (name == null || name.length() == 0)
return null;
SourcePositions positions = info.getTrees().getSourcePositions();
CompilationUnitTree cu = info.getCompilationUnit();
int start = (int)positions.getEndPosition(cu, ((InstanceOfTree) leaf).getType());
int end = (int)positions.getEndPosition(cu, pattern);
if (start == (-1) || end == (-1)) {
return null;
}
return findTokenWithText(info, name.toString(), start, end);
}
if ("BINDING_PATTERN".equals(leaf.getKind().name())) {
return findIdentifierSpanImplForBindingPattern(info, leaf, info.getCompilationUnit(), info.getTrees().getSourcePositions());
}
throw new IllegalArgumentException("Only MethodDecl, VariableDecl, MemberSelectTree, IdentifierTree, ParameterizedTypeTree, AnnotatedTypeTree, ClassDecl, BreakTree, ContinueTree, LabeledStatementTree and BindingPatternTree are accepted by this method. Got: " + leaf.getKind());
}
public static int[] findIdentifierSpan( final TreePath decl, final CompilationInfo info, final Document doc) {
final int[] result = new int[] {-1, -1};
Runnable r = new Runnable() {
public void run() {
Token<JavaTokenId> t = findIdentifierSpan(info, doc, decl);
if (t != null) {
result[0] = t.offset(null);
result[1] = t.offset(null) + t.length();
}
}
};
if (doc != null) {
doc.render(r);
} else {
r.run();
}
return result;
}
public static Token<JavaTokenId> findIdentifierSpan(final CompilationInfo info, final Document doc, final TreePath decl) {
@SuppressWarnings("unchecked")
final Token<JavaTokenId>[] result = new Token[1];
Runnable r = new Runnable() {
public void run() {
result[0] = findIdentifierSpanImpl(info, decl);
}
};
if (doc != null) {
doc.render(r);
} else {
r.run();
}
return result[0];
}
/**
* Finds end position among supplied Trees. The method accepts Tree, or List&lt? extends Tree> as the untyped
* vararg parameter. It's assumed that the caller passes varargs list is sorted starting from the rightmost tree or forest, so
* mutual relationships of items in the vararg list are not checked.
*
* The method returns end position of the first found tree; synthetic trees are ignored.
*
* @param cu compilation unit
* @param pos position information
* @param treeSets trees or forests
* @return end position or -1, if no suitable subtree was found.
*/
private static int findSubtreeEnd(CompilationUnitTree cu, SourcePositions pos, Object... treeSets) {
for (Object o : treeSets) {
if (o == null) {
continue;
}
if (o instanceof Tree) {
int offset = (int)pos.getEndPosition(cu, (Tree)o);
if (offset >= 0) {
return offset;
}
} else {
List<? extends Tree> set = (List<? extends Tree>)o;
if (!set.isEmpty()) {
// assume that the compiler will fake a single item in otherwise empty list; it should not add a fake item after some real Tree items.
Tree t = set.get(set.size() - 1);
int offset = (int)pos.getEndPosition(cu, t);
if (offset >= 0) {
return offset;
}
}
}
}
return -1;
}
private static int findBodyStartImpl(CompilationInfo info, Tree cltree, CompilationUnitTree cu, SourcePositions positions, Document doc) {
int start = (int)positions.getStartPosition(cu, cltree);
int end = (int)positions.getEndPosition(cu, cltree);
if (start == (-1) || end == (-1)) {
return -1;
}
int startPos = -1;
switch (cltree.getKind()) {
case CLASS: case INTERFACE: case ENUM: {
ClassTree ct = (ClassTree)cltree;
startPos = findSubtreeEnd(cu, positions,
ct.getImplementsClause(),
ct.getExtendsClause(),
ct.getTypeParameters(),
ct.getModifiers()
);
break;
}
case METHOD: {
// if the method contains some parameters, skip to the end of the parameter list
MethodTree mt = (MethodTree)cltree;
startPos = findSubtreeEnd(cu, positions, mt.getDefaultValue(), mt.getThrows(), mt.getParameters(), mt.getModifiers());
break;
}
}
if (startPos > start) {
start = startPos;
}
if (start > doc.getLength() || end > doc.getLength()) {
if (DEBUG) {
System.err.println("Log: position outside document: ");
System.err.println("decl = " + cltree);
System.err.println("startOffset = " + start);
System.err.println("endOffset = " + end);
Thread.dumpStack();
}
return (-1);
}
TokenHierarchy<JavaTokenId> th = (TokenHierarchy<JavaTokenId>)info.getTokenHierarchy();
TokenSequence<JavaTokenId> seq = (TokenSequence<JavaTokenId>)th.tokenSequence();
seq.move(start);
while (seq.moveNext()) {
if (seq.token().id() == JavaTokenId.LBRACE) {
return seq.offset();
}
}
return (-1);
}
public static int findBodyStart(final CompilationInfo info, final Tree cltree, final CompilationUnitTree cu, final SourcePositions positions, final Document doc) {
Kind kind = cltree.getKind();
if (!TreeUtilities.CLASS_TREE_KINDS.contains(kind) && kind != Kind.METHOD && !cltree.getKind().toString().equals("RECORD"))
throw new IllegalArgumentException("Unsupported kind: "+ kind);
final int[] result = new int[1];
doc.render(new Runnable() {
public void run() {
result[0] = findBodyStartImpl(info, cltree, cu, positions, doc);
}
});
return result[0];
}
private static int findLastBracketImpl(Tree tree, CompilationUnitTree cu, SourcePositions positions, Document doc) {
int start = (int)positions.getStartPosition(cu, tree);
int end = (int)positions.getEndPosition(cu, tree);
if (start == (-1) || end == (-1)) {
return -1;
}
if (start > doc.getLength() || end > doc.getLength()) {
if (DEBUG) {
System.err.println("Log: position outside document: ");
System.err.println("decl = " + tree);
System.err.println("startOffset = " + start);
System.err.println("endOffset = " + end);
Thread.dumpStack();
}
return (-1);
}
try {
String text = doc.getText(end - 1, 1);
if (text.charAt(0) == '}')
return end - 1;
} catch (BadLocationException e) {
LOG.log(Level.INFO, null, e);
}
return (-1);
}
public static int findLastBracket(final Tree tree, final CompilationUnitTree cu, final SourcePositions positions, final Document doc) {
final int[] result = new int[1];
doc.render(new Runnable() {
public void run() {
result[0] = findLastBracketImpl(tree, cu, positions, doc);
}
});
return result[0];
}
private static Token<JavaTokenId> createHighlightImpl(CompilationInfo info, Document doc, TreePath tree) {
Tree leaf = tree.getLeaf();
SourcePositions positions = info.getTrees().getSourcePositions();
CompilationUnitTree cu = info.getCompilationUnit();
//XXX: do not use instanceof:
if (leaf instanceof MethodTree || leaf instanceof VariableTree || leaf instanceof ClassTree
|| leaf instanceof MemberSelectTree || leaf instanceof AnnotatedTypeTree || leaf instanceof MemberReferenceTree
|| "BINDING_PATTERN".equals(leaf.getKind().name())) {
return findIdentifierSpan(info, doc, tree);
}
int start = (int) positions.getStartPosition(cu, leaf);
int end = (int) positions.getEndPosition(cu, leaf);
if (start == Diagnostic.NOPOS || end == Diagnostic.NOPOS) {
return null;
}
TokenHierarchy<?> th = info.getTokenHierarchy();
TokenSequence<JavaTokenId> ts = th.tokenSequence(JavaTokenId.language());
if (ts.move(start) == Integer.MAX_VALUE) {
return null;
}
if (ts.moveNext()) {
Token<JavaTokenId> token = ts.token();
if (ts.offset() == start && token != null) {
final JavaTokenId id = token.id();
if (id == JavaTokenId.IDENTIFIER) {
return token;
}
if (id == JavaTokenId.THIS || id == JavaTokenId.SUPER) {
return ts.offsetToken();
}
}
}
return null;
}
public static Token<JavaTokenId> getToken(final CompilationInfo info, final Document doc, final TreePath tree) {
@SuppressWarnings("unchecked")
final Token<JavaTokenId>[] result = new Token[1];
doc.render(new Runnable() {
public void run() {
result[0] = createHighlightImpl(info, doc, tree);
}
});
return result[0];
}
private static final Set<String> keywords;
private static final Set<String> nonCtorKeywords;
static {
keywords = new HashSet<String>();
keywords.add("true");
keywords.add("false");
keywords.add("null");
keywords.add("this");
keywords.add("super");
keywords.add("class");
nonCtorKeywords = new HashSet<String>(keywords);
nonCtorKeywords.remove("this");
nonCtorKeywords.remove("super");
}
public static boolean isKeyword(Tree tree) {
if (tree.getKind() == Kind.IDENTIFIER) {
return keywords.contains(((IdentifierTree) tree).getName().toString());
}
if (tree.getKind() == Kind.MEMBER_SELECT) {
return keywords.contains(((MemberSelectTree) tree).getIdentifier().toString());
}
return false;
}
public static boolean isNonCtorKeyword(Tree tree) {
if (tree.getKind() == Kind.IDENTIFIER) {
return nonCtorKeywords.contains(((IdentifierTree) tree).getName().toString());
}
if (tree.getKind() == Kind.MEMBER_SELECT) {
return nonCtorKeywords.contains(((MemberSelectTree) tree).getIdentifier().toString());
}
return false;
}
private static final Set<ElementKind> LOCAL_ELEMENT_KINDS = EnumSet.of(ElementKind.PARAMETER, ElementKind.LOCAL_VARIABLE, ElementKind.EXCEPTION_PARAMETER, ElementKind.RESOURCE_VARIABLE);
static {
try {
LOCAL_ELEMENT_KINDS.add(ElementKind.valueOf(TreeShims.BINDING_VARIABLE));
} catch (IllegalArgumentException ignore) {}
}
public static boolean isPrivateElement(Element el) {
return LOCAL_ELEMENT_KINDS.contains(el.getKind()) || el.getModifiers().contains(Modifier.PRIVATE);
}
}