blob: 3be2810a0ecaa4e66aec0568f8e6ca28e0120142 [file] [log] [blame]
/*
* DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
*
* Copyright 1997-2010 Oracle and/or its affiliates. All rights reserved.
*
* Oracle and Java are registered trademarks of Oracle and/or its affiliates.
* Other names may be trademarks of their respective owners.
*
* The contents of this file are subject to the terms of either the GNU
* General Public License Version 2 only ("GPL") or the Common
* Development and Distribution License("CDDL") (collectively, the
* "License"). You may not use this file except in compliance with the
* License. You can obtain a copy of the License at
* http://www.netbeans.org/cddl-gplv2.html
* or nbbuild/licenses/CDDL-GPL-2-CP. See the License for the
* specific language governing permissions and limitations under the
* License. When distributing the software, include this License Header
* Notice in each file and include the License file at
* nbbuild/licenses/CDDL-GPL-2-CP. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the GPL Version 2 section of the License file that
* accompanied this code. If applicable, add the following below the
* License Header, with the fields enclosed by brackets [] replaced by
* your own identifying information:
* "Portions Copyrighted [year] [name of copyright owner]"
*
* Contributor(s):
*
* The Original Software is NetBeans. The Initial Developer of the Original
* Software is Sun Microsystems, Inc. Portions Copyright 1997-2008 Sun
* Microsystems, Inc. All Rights Reserved.
*
* If you wish your version of this file to be governed by only the CDDL
* or only the GPL Version 2, indicate your decision by adding
* "[Contributor] elects to include this software in this distribution
* under the [CDDL or GPL Version 2] license." If you do not indicate a
* single choice of license, a recipient has the option to distribute
* your version of this file under either the CDDL, the GPL Version 2 or
* to extend the choice of license to its licensees as provided above.
* However, if you add GPL Version 2 code and therefore, elected the GPL
* Version 2 license, then the option applies only if the new code is
* made subject to such option by the copyright holder.
*/
package org.netbeans.modules.java.editor.semantic;
import com.sun.source.tree.AnnotationTree;
import com.sun.source.tree.ArrayAccessTree;
import com.sun.source.tree.ArrayTypeTree;
import com.sun.source.tree.AssertTree;
import com.sun.source.tree.AssignmentTree;
import com.sun.source.tree.BinaryTree;
import com.sun.source.tree.CaseTree;
import com.sun.source.tree.CatchTree;
import com.sun.source.tree.ClassTree;
import com.sun.source.tree.CompilationUnitTree;
import com.sun.source.tree.CompoundAssignmentTree;
import com.sun.source.tree.ConditionalExpressionTree;
import com.sun.source.tree.EnhancedForLoopTree;
import com.sun.source.tree.ExpressionStatementTree;
import com.sun.source.tree.ExpressionTree;
import com.sun.source.tree.ForLoopTree;
import com.sun.source.tree.IdentifierTree;
import com.sun.source.tree.ImportTree;
import com.sun.source.tree.InstanceOfTree;
import com.sun.source.tree.MemberSelectTree;
import com.sun.source.tree.MethodInvocationTree;
import com.sun.source.tree.MethodTree;
import com.sun.source.tree.NewArrayTree;
import com.sun.source.tree.NewClassTree;
import com.sun.source.tree.ParameterizedTypeTree;
import com.sun.source.tree.ParenthesizedTree;
import com.sun.source.tree.ReturnTree;
import com.sun.source.tree.ThrowTree;
import com.sun.source.tree.Tree;
import com.sun.source.tree.Tree.Kind;
import com.sun.source.tree.TypeCastTree;
import com.sun.source.tree.TypeParameterTree;
import com.sun.source.tree.UnaryTree;
import com.sun.source.tree.UnionTypeTree;
import com.sun.source.tree.VariableTree;
import com.sun.source.tree.WildcardTree;
import com.sun.source.util.SourcePositions;
import com.sun.source.util.TreePath;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.EnumSet;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.logging.Level;
import java.util.logging.Logger;
import javax.lang.model.element.Element;
import javax.lang.model.element.ElementKind;
import javax.lang.model.element.ExecutableElement;
import javax.lang.model.element.Modifier;
import javax.lang.model.type.TypeKind;
import javax.swing.SwingUtilities;
import javax.swing.text.Document;
import org.netbeans.api.java.source.CompilationInfo;
import org.netbeans.api.java.source.JavaParserResultTask;
import org.netbeans.api.java.source.JavaSource.Phase;
import org.netbeans.api.java.source.TreePathHandle;
import org.netbeans.api.java.source.TreeUtilities;
import org.netbeans.api.java.source.support.CancellableTreePathScanner;
import org.netbeans.api.lexer.Token;
import org.netbeans.api.lexer.TokenHierarchy;
import org.netbeans.modules.editor.NbEditorUtilities;
import org.netbeans.modules.java.editor.imports.UnusedImports;
import org.netbeans.modules.java.editor.semantic.ColoringAttributes.Coloring;
import org.netbeans.modules.parsing.spi.Parser.Result;
import org.netbeans.modules.parsing.spi.Scheduler;
import org.netbeans.modules.parsing.spi.SchedulerEvent;
import org.netbeans.modules.parsing.spi.TaskIndexingMode;
import org.netbeans.spi.editor.highlighting.support.OffsetsBag;
import org.netbeans.spi.editor.hints.ErrorDescription;
import org.openide.filesystems.FileObject;
import org.openide.filesystems.FileUtil;
import org.openide.loaders.DataObject;
/**
*
* @author Jan Lahoda
*/
public class SemanticHighlighter extends JavaParserResultTask {
public static List<TreePathHandle> computeUnusedImports(CompilationInfo info) throws IOException {
final List<TreePathHandle> result = new ArrayList<TreePathHandle>();
for (TreePath unused : UnusedImports.process(info, new AtomicBoolean())) {
result.add(TreePathHandle.create(unused, info));
}
return result;
}
private FileObject file;
//XXX: correct rescheduling when troubles traversing token list!
// private SemanticHighlighterFactory fact;
private AtomicBoolean cancel = new AtomicBoolean();
// SemanticHighlighter(FileObject file) {
// this(file, null);
// }
//
SemanticHighlighter(FileObject file/*, SemanticHighlighterFactory fact*/) {
super(Phase.RESOLVED, TaskIndexingMode.ALLOWED_DURING_SCAN);
this.file = file;
// this.fact = fact;
}
@Override
public void run(Result result, SchedulerEvent event) {
CompilationInfo info = CompilationInfo.get(result);
if (info == null) {
return ;
}
cancel.set(false);
final Document doc = result.getSnapshot().getSource().getDocument(false);
if (!verifyDocument(doc)) return;
if (process(info, doc)/* && fact != null*/) {
// fact.rescheduleImpl(file);
}
}
private static boolean verifyDocument(final Document doc) {
if (doc == null) {
Logger.getLogger(SemanticHighlighter.class.getName()).log(Level.FINE, "SemanticHighlighter: Cannot get document!");
return false;
}
final boolean[] tokenSequenceNull = new boolean[1];
doc.render(new Runnable() {
public void run() {
tokenSequenceNull[0] = (TokenHierarchy.get(doc).tokenSequence() == null);
}
});
if (tokenSequenceNull[0]) {
return false;
}
return true;
}
public void cancel() {
cancel.set(true);
}
@Override
public int getPriority() {
return 100;
}
@Override
public Class<? extends Scheduler> getSchedulerClass() {
return Scheduler.EDITOR_SENSITIVE_TASK_SCHEDULER;
}
boolean process(CompilationInfo info, final Document doc) {
return process(info, doc, ERROR_DESCRIPTION_SETTER);
}
static Coloring collection2Coloring(Collection<ColoringAttributes> attr) {
Coloring c = ColoringAttributes.empty();
for (ColoringAttributes a : attr) {
c = ColoringAttributes.add(c, a);
}
return c;
}
boolean process(CompilationInfo info, final Document doc, ErrorDescriptionSetter setter) {
DetectorVisitor v = new DetectorVisitor(info, doc, cancel);
long start = System.currentTimeMillis();
Map<Token, Coloring> newColoring = new IdentityHashMap<Token, Coloring>();
List<ErrorDescription> errors = new ArrayList<ErrorDescription>();
CompilationUnitTree cu = info.getCompilationUnit();
v.scan(cu, null);
if (cancel.get())
return true;
boolean computeUnusedImports = "text/x-java".equals(FileUtil.getMIMEType(info.getFileObject()));
final List<TreePathHandle> allUnusedImports = computeUnusedImports ? new ArrayList<TreePathHandle>() : null;
OffsetsBag imports = computeUnusedImports ? new OffsetsBag(doc) : null;
if (computeUnusedImports) {
Coloring unused = ColoringAttributes.add(ColoringAttributes.empty(), ColoringAttributes.UNUSED);
Collection<TreePath> unusedImports = UnusedImports.process(info, cancel);
if (unusedImports == null) return true;
for (TreePath tree : unusedImports) {
if (cancel.get()) {
return true;
}
//XXX: finish
final int startPos = (int) info.getTrees().getSourcePositions().getStartPosition(cu, tree.getLeaf());
final int endPos = (int) info.getTrees().getSourcePositions().getEndPosition(cu, tree.getLeaf());
imports.addHighlight(startPos, endPos, ColoringManager.getColoringImpl(unused));
TreePathHandle handle = TreePathHandle.create(tree, info);
allUnusedImports.add(handle);
}
}
Map<Token, Coloring> oldColors = LexerBasedHighlightLayer.getLayer(SemanticHighlighter.class, doc).getColorings();
Map<Token, Coloring> removedTokens = new IdentityHashMap<Token, Coloring>(oldColors);
Set<Token> addedTokens = new HashSet<Token>();
for (Element decl : v.type2Uses.keySet()) {
if (cancel.get())
return true;
List<Use> uses = v.type2Uses.get(decl);
for (Use u : uses) {
if (u.spec == null)
continue;
if (u.type.contains(UseTypes.DECLARATION) && org.netbeans.modules.java.editor.semantic.Utilities.isPrivateElement(decl)) {
if ((decl.getKind().isField() && !isSerialVersionUID(info, decl)) || isLocalVariableClosure(decl)) {
if (!hasAllTypes(uses, EnumSet.of(UseTypes.READ, UseTypes.WRITE))) {
u.spec.add(ColoringAttributes.UNUSED);
}
}
if ((decl.getKind() == ElementKind.CONSTRUCTOR && !decl.getModifiers().contains(Modifier.PRIVATE)) || decl.getKind() == ElementKind.METHOD) {
if (!hasAllTypes(uses, EnumSet.of(UseTypes.EXECUTE))) {
u.spec.add(ColoringAttributes.UNUSED);
}
}
if (decl.getKind().isClass() || decl.getKind().isInterface()) {
if (!hasAllTypes(uses, EnumSet.of(UseTypes.CLASS_USE))) {
u.spec.add(ColoringAttributes.UNUSED);
}
}
}
Coloring c = collection2Coloring(u.spec);
Token t = v.tree2Token.get(u.tree.getLeaf());
if (t != null) {
newColoring.put(t, c);
Coloring oldColoring = removedTokens.remove(t);
if (oldColoring == null || !oldColoring.equals(c)) {
addedTokens.add(t);
}
}
}
}
if (cancel.get())
return true;
if (computeUnusedImports) {
setter.setErrors(doc, errors, allUnusedImports);
setter.setHighlights(doc, imports);
}
setter.setColorings(doc, newColoring, addedTokens, removedTokens.keySet());
Logger.getLogger("TIMER").log(Level.FINE, "Semantic",
new Object[] {NbEditorUtilities.getFileObject(doc), System.currentTimeMillis() - start});
return false;
}
private boolean hasAllTypes(List<Use> uses, Collection<UseTypes> types) {
EnumSet e = EnumSet.copyOf(types);
for (Use u : uses) {
if (types.isEmpty()) {
return true;
}
types.removeAll(u.type);
}
return types.isEmpty();
}
private enum UseTypes {
READ, WRITE, EXECUTE, DECLARATION, CLASS_USE;
}
private static boolean isLocalVariableClosure(Element el) {
return el.getKind() == ElementKind.PARAMETER || el.getKind() == ElementKind.LOCAL_VARIABLE
|| el.getKind() == ElementKind.RESOURCE_VARIABLE || el.getKind() == ElementKind.EXCEPTION_PARAMETER;
}
/** Detects static final long SerialVersionUID
* @return true if element is final static long serialVersionUID
*/
private static boolean isSerialVersionUID(CompilationInfo info, Element el) {
if (el.getKind().isField() && el.getModifiers().contains(Modifier.FINAL)
&& el.getModifiers().contains(Modifier.STATIC)
&& info.getTypes().getPrimitiveType(TypeKind.LONG).equals(el.asType())
&& el.getSimpleName().toString().equals("serialVersionUID"))
return true;
else
return false;
}
private static class Use {
private Collection<UseTypes> type;
private TreePath tree;
private Collection<ColoringAttributes> spec;
public Use(Collection<UseTypes> type, TreePath tree, Collection<ColoringAttributes> spec) {
this.type = type;
this.tree = tree;
this.spec = spec;
}
@Override
public String toString() {
return "Use: " + type;
}
}
private static class DetectorVisitor extends CancellableTreePathScanner<Void, EnumSet<UseTypes>> {
private org.netbeans.api.java.source.CompilationInfo info;
private Document doc;
private Map<Element, List<Use>> type2Uses;
private Map<Tree, Token> tree2Token;
private TokenList tl;
private long memberSelectBypass = -1;
private SourcePositions sourcePositions;
private ExecutableElement recursionDetector;
private DetectorVisitor(org.netbeans.api.java.source.CompilationInfo info, final Document doc, AtomicBoolean cancel) {
super(cancel);
this.info = info;
this.doc = doc;
type2Uses = new HashMap<Element, List<Use>>();
tree2Token = new IdentityHashMap<Tree, Token>();
tl = new TokenList(info, doc, cancel);
this.sourcePositions = info.getTrees().getSourcePositions();
// this.pos = pos;
}
private void firstIdentifier(String name) {
tl.firstIdentifier(getCurrentPath(), name, tree2Token);
}
@Override
public Void visitAssignment(AssignmentTree tree, EnumSet<UseTypes> d) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getVariable()), EnumSet.of(UseTypes.WRITE));
Tree expr = tree.getExpression();
if (expr instanceof IdentifierTree) {
TreePath tp = new TreePath(getCurrentPath(), expr);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.READ));
}
scan(tree.getVariable(), EnumSet.of(UseTypes.WRITE));
scan(tree.getExpression(), EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitCompoundAssignment(CompoundAssignmentTree tree, EnumSet<UseTypes> d) {
Set<UseTypes> useTypes = EnumSet.of(UseTypes.WRITE);
if (d != null) {
useTypes.addAll(d);
}
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getVariable()), useTypes);
Tree expr = tree.getExpression();
if (expr instanceof IdentifierTree) {
TreePath tp = new TreePath(getCurrentPath(), expr);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.READ));
}
scan(tree.getVariable(), EnumSet.of(UseTypes.WRITE));
scan(tree.getExpression(), EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitReturn(ReturnTree tree, EnumSet<UseTypes> d) {
if (tree.getExpression() instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getExpression()), EnumSet.of(UseTypes.READ));
}
super.visitReturn(tree, EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitMemberSelect(MemberSelectTree tree, EnumSet<UseTypes> d) {
long memberSelectBypassLoc = memberSelectBypass;
memberSelectBypass = -1;
Tree expr = tree.getExpression();
if (expr instanceof IdentifierTree) {
TreePath tp = new TreePath(getCurrentPath(), expr);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.READ));
}
Element el = info.getTrees().getElement(getCurrentPath());
if (el != null && el.getKind().isField()) {
handlePossibleIdentifier(getCurrentPath(), d == null ? EnumSet.of(UseTypes.READ) : d);
}
if (el != null && (el.getKind().isClass() || el.getKind().isInterface()) &&
getCurrentPath().getParentPath().getLeaf().getKind() != Kind.NEW_CLASS) {
handlePossibleIdentifier(getCurrentPath(), EnumSet.of(UseTypes.CLASS_USE));
}
// System.err.println("XXXX=" + tree.toString());
// System.err.println("YYYY=" + info.getElement(tree));
super.visitMemberSelect(tree, null);
tl.moveToEnd(tree.getExpression());
if (memberSelectBypassLoc != (-1)) {
tl.moveToOffset(memberSelectBypassLoc);
}
firstIdentifier(tree.getIdentifier().toString());
return null;
}
private void addModifiers(Element decl, Collection<ColoringAttributes> c) {
if (decl.getModifiers().contains(Modifier.STATIC)) {
c.add(ColoringAttributes.STATIC);
}
if (decl.getModifiers().contains(Modifier.ABSTRACT) && !decl.getKind().isInterface()) {
c.add(ColoringAttributes.ABSTRACT);
}
boolean accessModifier = false;
if (decl.getModifiers().contains(Modifier.PUBLIC)) {
c.add(ColoringAttributes.PUBLIC);
accessModifier = true;
}
if (decl.getModifiers().contains(Modifier.PROTECTED)) {
c.add(ColoringAttributes.PROTECTED);
accessModifier = true;
}
if (decl.getModifiers().contains(Modifier.PRIVATE)) {
c.add(ColoringAttributes.PRIVATE);
accessModifier = true;
}
if (!accessModifier && !isLocalVariableClosure(decl)) {
c.add(ColoringAttributes.PACKAGE_PRIVATE);
}
if (info.getElements().isDeprecated(decl)) {
c.add(ColoringAttributes.DEPRECATED);
}
}
private Collection<ColoringAttributes> getMethodColoring(ExecutableElement mdecl, boolean nct) {
Collection<ColoringAttributes> c = new ArrayList<ColoringAttributes>();
addModifiers(mdecl, c);
if (mdecl.getKind() == ElementKind.CONSTRUCTOR) {
c.add(ColoringAttributes.CONSTRUCTOR);
//#146820:
if (nct && mdecl.getEnclosingElement() != null && info.getElements().isDeprecated(mdecl.getEnclosingElement())) {
c.add(ColoringAttributes.DEPRECATED);
}
} else
c.add(ColoringAttributes.METHOD);
return c;
}
private Collection<ColoringAttributes> getVariableColoring(Element decl) {
Collection<ColoringAttributes> c = new ArrayList<ColoringAttributes>();
addModifiers(decl, c);
if (decl.getKind().isField()) {
c.add(ColoringAttributes.FIELD);
return c;
}
if (decl.getKind() == ElementKind.LOCAL_VARIABLE || decl.getKind() == ElementKind.RESOURCE_VARIABLE
|| decl.getKind() == ElementKind.EXCEPTION_PARAMETER) {
c.add(ColoringAttributes.LOCAL_VARIABLE);
return c;
}
if (decl.getKind() == ElementKind.PARAMETER) {
c.add(ColoringAttributes.PARAMETER);
return c;
}
assert false;
return null;
}
private static final Set<Kind> LITERALS = EnumSet.of(Kind.BOOLEAN_LITERAL, Kind.CHAR_LITERAL, Kind.DOUBLE_LITERAL, Kind.FLOAT_LITERAL, Kind.INT_LITERAL, Kind.LONG_LITERAL, Kind.STRING_LITERAL);
private void handlePossibleIdentifier(TreePath expr, Collection<UseTypes> type) {
handlePossibleIdentifier(expr, type, null, false, false);
}
private void handlePossibleIdentifier(TreePath expr, Collection<UseTypes> type, Element decl, boolean providesDecl, boolean nct) {
if (Utilities.isKeyword(expr.getLeaf())) {
//ignore keywords:
return ;
}
if (expr.getLeaf().getKind() == Kind.PRIMITIVE_TYPE) {
//ignore primitive types:
return ;
}
if (LITERALS.contains(expr.getLeaf().getKind())) {
//ignore literals:
return ;
}
decl = !providesDecl ? info.getTrees().getElement(expr) : decl;
Collection<ColoringAttributes> c = null;
//causes NPE later, as decl is put into list of declarations to handle:
// if (decl == null) {
// c = Collections.singletonList(ColoringAttributes.UNDEFINED);
// }
if (decl != null && (decl.getKind().isField() || isLocalVariableClosure(decl))) {
c = getVariableColoring(decl);
}
if (decl != null && decl instanceof ExecutableElement) {
c = getMethodColoring((ExecutableElement) decl, nct);
}
if (decl != null && (decl.getKind().isClass() || decl.getKind().isInterface())) {
//class use make look like read variable access:
if (type.contains(UseTypes.READ)) {
type = EnumSet.copyOf(type);
type.remove(UseTypes.READ);
type.add(UseTypes.CLASS_USE);
}
c = new ArrayList<ColoringAttributes>();
addModifiers(decl, c);
switch (decl.getKind()) {
case CLASS: c.add(ColoringAttributes.CLASS); break;
case INTERFACE: c.add(ColoringAttributes.INTERFACE); break;
case ANNOTATION_TYPE: c.add(ColoringAttributes.ANNOTATION_TYPE); break;
case ENUM: c.add(ColoringAttributes.ENUM); break;
}
}
if (decl != null && type.contains(UseTypes.DECLARATION)) {
if (c == null) {
c = new ArrayList<ColoringAttributes>();
}
c.add(ColoringAttributes.DECLARATION);
}
if (c != null) {
addUse(decl, type, expr, c);
}
}
private void addUse(Element decl, Collection<UseTypes> useTypes, TreePath t, Collection<ColoringAttributes> c) {
if (decl == recursionDetector) {
useTypes.remove(UseTypes.EXECUTE); //recursive execution is not use
}
List<Use> uses = type2Uses.get(decl);
if (uses == null) {
type2Uses.put(decl, uses = new ArrayList<Use>());
}
Use u = new Use(useTypes, t, c);
uses.add(u);
}
@Override
public Void visitTypeCast(TypeCastTree tree, EnumSet<UseTypes> d) {
Tree expr = tree.getExpression();
if (expr.getKind() == Kind.IDENTIFIER) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), expr), EnumSet.of(UseTypes.READ));
}
Tree cast = tree.getType();
if (cast.getKind() == Kind.IDENTIFIER) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), cast), EnumSet.of(UseTypes.READ));
}
super.visitTypeCast(tree, d);
return null;
}
@Override
public Void visitInstanceOf(InstanceOfTree tree, EnumSet<UseTypes> d) {
Tree expr = tree.getExpression();
if (expr instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), expr), EnumSet.of(UseTypes.READ));
}
TreePath tp = new TreePath(getCurrentPath(), tree.getType());
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
super.visitInstanceOf(tree, null);
//TODO: should be considered
return null;
}
@Override
public Void visitCompilationUnit(CompilationUnitTree tree, EnumSet<UseTypes> d) {
//ignore package X.Y.Z;:
//scan(tree.getPackageDecl(), p);
tl.moveBefore(tree.getImports());
scan(tree.getImports(), d);
tl.moveBefore(tree.getPackageAnnotations());
scan(tree.getPackageAnnotations(), d);
tl.moveToEnd(tree.getImports());
scan(tree.getTypeDecls(), d);
return null;
}
private long startOf(List<? extends Tree> trees) {
if (trees.isEmpty()) return -1;
return sourcePositions.getStartPosition(info.getCompilationUnit(), trees.get(0));
}
private void handleMethodTypeArguments(TreePath method, List<? extends Tree> tArgs) {
//the type arguments are before the last identifier in the select, so we should return there:
//not very efficient, though:
tl.moveBefore(tArgs);
for (Tree expr : tArgs) {
if (expr instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(method, expr), EnumSet.of(UseTypes.CLASS_USE));
}
}
}
@Override
public Void visitMethodInvocation(MethodInvocationTree tree, EnumSet<UseTypes> d) {
Tree possibleIdent = tree.getMethodSelect();
boolean handled = false;
if (possibleIdent.getKind() == Kind.IDENTIFIER) {
//handle "this" and "super" constructors:
String ident = ((IdentifierTree) possibleIdent).getName().toString();
if ("super".equals(ident) || "this".equals(ident)) { //NOI18N
Element resolved = info.getTrees().getElement(getCurrentPath());
addUse(resolved, EnumSet.of(UseTypes.EXECUTE), null, null);
handled = true;
}
}
if (!handled) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), possibleIdent), EnumSet.of(UseTypes.EXECUTE));
}
List<? extends Tree> ta = tree.getTypeArguments();
long afterTypeArguments = ta.isEmpty() ? -1 : info.getTrees().getSourcePositions().getEndPosition(info.getCompilationUnit(), ta.get(ta.size() - 1));
switch (tree.getMethodSelect().getKind()) {
case IDENTIFIER:
case MEMBER_SELECT:
memberSelectBypass = afterTypeArguments;
scan(tree.getMethodSelect(), null);
memberSelectBypass = -1;
break;
default:
//todo: log
scan(tree.getMethodSelect(), null);
}
handleMethodTypeArguments(getCurrentPath(), ta);
scan(tree.getTypeArguments(), null);
// if (tree.getMethodSelect().getKind() == Kind.MEMBER_SELECT && tree2Token.get(tree.getMethodSelect()) == null) {
//// if (ts.moveNext()) ???
// firstIdentifier(((MemberSelectTree) tree.getMethodSelect()).getIdentifier().toString());
// }
for (Tree expr : tree.getArguments()) {
if (expr instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), expr), EnumSet.of(UseTypes.READ));
}
}
scan(tree.getArguments(), EnumSet.of(UseTypes.READ));
// super.visitMethodInvocation(tree, null);
return null;
}
@Override
public Void visitIdentifier(IdentifierTree tree, EnumSet<UseTypes> d) {
if (info.getTreeUtilities().isSynthetic(getCurrentPath()))
return null;
// if ("l".equals(tree.toString())) {
// Thread.dumpStack();
// }
// handlePossibleIdentifier(tree);
// //also possible type: (like in Collections.EMPTY_LIST):
// resolveType(tree);
// Thread.dumpStack();
tl.moveToOffset(sourcePositions.getStartPosition(info.getCompilationUnit(), tree));
if (memberSelectBypass != (-1)) {
tl.moveToOffset(memberSelectBypass);
memberSelectBypass = -1;
}
tl.identifierHere(tree, tree2Token);
if (d != null) {
handlePossibleIdentifier(getCurrentPath(), d);
}
super.visitIdentifier(tree, null);
return null;
}
//
@Override
public Void visitMethod(MethodTree tree, EnumSet<UseTypes> d) {
if (info.getTreeUtilities().isSynthetic(getCurrentPath())) {
return super.visitMethod(tree, d);
}
// Element decl = pi.getAttribution().getElement(tree);
//
// if (decl != null) {
// assert decl instanceof ExecutableElement;
//
// Coloring c = getMethodColoring((ExecutableElement) decl);
// HighlightImpl h = createHighlight(decl.getSimpleName(), tree, c, null);
//
// if (h != null) {
// highlights.add(h);
// }
// }
//#170338: constructor without modifiers:
tl.moveToOffset(sourcePositions.getStartPosition(info.getCompilationUnit(), tree));
handlePossibleIdentifier(getCurrentPath(), EnumSet.of(UseTypes.DECLARATION));
for (Tree t : tree.getThrows()) {
TreePath tp = new TreePath(getCurrentPath(), t);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
}
EnumSet<UseTypes> paramsUseTypes;
Element el = info.getTrees().getElement(getCurrentPath());
if (el != null && (el.getModifiers().contains(Modifier.ABSTRACT) || el.getModifiers().contains(Modifier.NATIVE) || !el.getModifiers().contains(Modifier.PRIVATE))) {
paramsUseTypes = EnumSet.of(UseTypes.WRITE, UseTypes.READ);
} else {
paramsUseTypes = EnumSet.of(UseTypes.WRITE);
}
scan(tree.getModifiers(), null);
tl.moveToEnd(tree.getModifiers());
scan(tree.getTypeParameters(), null);
tl.moveToEnd(tree.getTypeParameters());
scan(tree.getReturnType(), EnumSet.of(UseTypes.CLASS_USE));
tl.moveToEnd(tree.getReturnType());
String name;
if (tree.getReturnType() != null) {
//method:
name = tree.getName().toString();
} else {
//constructor:
TreePath tp = getCurrentPath();
while (tp != null && !TreeUtilities.CLASS_TREE_KINDS.contains(tp.getLeaf().getKind())) {
tp = tp.getParentPath();
}
if (tp != null && TreeUtilities.CLASS_TREE_KINDS.contains(tp.getLeaf().getKind())) {
name = ((ClassTree) tp.getLeaf()).getSimpleName().toString();
} else {
name = null;
}
}
if (name != null) {
firstIdentifier(name);
}
scan(tree.getParameters(), paramsUseTypes);
scan(tree.getThrows(), null);
scan(tree.getDefaultValue(), null);
recursionDetector = (el != null && el.getKind() == ElementKind.METHOD) ? (ExecutableElement) el : null;
scan(tree.getBody(), null);
recursionDetector = null;
return null;
}
@Override
public Void visitExpressionStatement(ExpressionStatementTree tree, EnumSet<UseTypes> d) {
// if (tree instanceof IdentifierTree) {
// handlePossibleIdentifier(tree, EnumSet.of(UseTypes.READ));
// }
super.visitExpressionStatement(tree, null);
return null;
}
@Override
public Void visitParenthesized(ParenthesizedTree tree, EnumSet<UseTypes> d) {
ExpressionTree expr = tree.getExpression();
if (expr instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), expr), EnumSet.of(UseTypes.READ));
}
super.visitParenthesized(tree, d);
return null;
}
@Override
public Void visitEnhancedForLoop(EnhancedForLoopTree tree, EnumSet<UseTypes> d) {
scan(tree.getVariable(), EnumSet.of(UseTypes.WRITE));
if (tree.getExpression().getKind() == Kind.IDENTIFIER)
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getExpression()), EnumSet.of(UseTypes.READ));
scan(tree.getExpression(), null);
scan(tree.getStatement(), null);
return null;
}
private boolean isStar(ImportTree tree) {
Tree qualIdent = tree.getQualifiedIdentifier();
if (qualIdent == null || qualIdent.getKind() == Kind.IDENTIFIER) {
return false;
}
return ((MemberSelectTree) qualIdent).getIdentifier().contentEquals("*");
}
@Override
public Void visitVariable(VariableTree tree, EnumSet<UseTypes> d) {
tl.moveToOffset(sourcePositions.getStartPosition(info.getCompilationUnit(), tree));
TreePath type = new TreePath(getCurrentPath(), tree.getType());
if (type.getLeaf() instanceof ArrayTypeTree) {
type = new TreePath(type, ((ArrayTypeTree) type.getLeaf()).getType());
}
if (type.getLeaf().getKind() == Kind.IDENTIFIER)
handlePossibleIdentifier(type, EnumSet.of(UseTypes.CLASS_USE));
Collection<UseTypes> uses = null;
Element e = info.getTrees().getElement(getCurrentPath());
if (tree.getInitializer() != null) {
uses = EnumSet.of(UseTypes.DECLARATION, UseTypes.WRITE);
if (tree.getInitializer().getKind() == Kind.IDENTIFIER)
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getInitializer()), EnumSet.of(UseTypes.READ));
} else {
if (e != null && e.getKind() == ElementKind.FIELD) {
uses = EnumSet.of(UseTypes.DECLARATION, UseTypes.WRITE);
} else {
uses = EnumSet.of(UseTypes.DECLARATION);
}
}
if (d != null) {
Set<UseTypes> ut = new HashSet<UseTypes>();
ut.addAll(uses);
ut.addAll(d);
uses = EnumSet.copyOf(ut);
}
handlePossibleIdentifier(getCurrentPath(), uses);
scan(tree.getModifiers(), null);
tl.moveToEnd(tree.getModifiers());
scan(tree.getType(), null);
int[] span = info.getTreeUtilities().findNameSpan(tree);
if (span != null)
tl.moveToOffset(span[0]);
else
tl.moveToEnd(tree.getType());
// System.err.println("tree.getName().toString()=" + tree.getName().toString());
firstIdentifier(tree.getName().toString());
tl.moveNext();
scan(tree.getInitializer(), EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitAnnotation(AnnotationTree tree, EnumSet<UseTypes> d) {
// System.err.println("tree.getType()= " + tree.toString());
// System.err.println("tree.getType()= " + tree.getClass());
//
TreePath tp = new TreePath(getCurrentPath(), tree.getAnnotationType());
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
super.visitAnnotation(tree, EnumSet.noneOf(UseTypes.class));
//TODO: maybe should be considered
return null;
}
@Override
public Void visitNewClass(NewClassTree tree, EnumSet<UseTypes> d) {
// if (info.getTreeUtilities().isSynthetic(getCurrentPath()))
// return null;
//
Tree exp = tree.getEnclosingExpression();
if (exp instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), exp), EnumSet.of(UseTypes.READ));
}
TreePath tp;
Tree ident = tree.getIdentifier();
if (ident.getKind() == Kind.PARAMETERIZED_TYPE) {
tp = new TreePath(new TreePath(getCurrentPath(), ident), ((ParameterizedTypeTree) ident).getType());
} else {
tp = new TreePath(getCurrentPath(), ident);
}
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.EXECUTE), info.getTrees().getElement(getCurrentPath()), true, true);
Element clazz = info.getTrees().getElement(tp);
if (clazz != null) {
addUse(clazz, EnumSet.of(UseTypes.CLASS_USE), null, null);
}
for (Tree expr : tree.getArguments()) {
if (expr instanceof IdentifierTree) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), expr), EnumSet.of(UseTypes.READ));
}
}
scan(tree.getEnclosingExpression(), null);
scan(tree.getIdentifier(), null);
scan(tree.getTypeArguments(), null);
scan(tree.getArguments(), EnumSet.of(UseTypes.READ));
scan(tree.getClassBody(), null);
return null;
}
@Override
public Void visitParameterizedType(ParameterizedTypeTree tree, EnumSet<UseTypes> d) {
boolean alreadyHandled = false;
if (getCurrentPath().getParentPath().getLeaf().getKind() == Kind.NEW_CLASS) {
NewClassTree nct = (NewClassTree) getCurrentPath().getParentPath().getLeaf();
alreadyHandled = nct.getTypeArguments().contains(tree) || nct.getIdentifier() == tree;
}
if (getCurrentPath().getParentPath().getParentPath().getLeaf().getKind() == Kind.NEW_CLASS) {
NewClassTree nct = (NewClassTree) getCurrentPath().getParentPath().getParentPath().getLeaf();
Tree leafToTest = getCurrentPath().getParentPath().getLeaf();
alreadyHandled = nct.getTypeArguments().contains(leafToTest) || nct.getIdentifier() == leafToTest;
}
if (!alreadyHandled) {
//NewClass has already been handled as part of visitNewClass:
TreePath tp = new TreePath(getCurrentPath(), tree.getType());
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
}
for (Tree t : tree.getTypeArguments()) {
TreePath tp = new TreePath(getCurrentPath(), t);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
// HighlightImpl h = createHighlight("", t, TYPE_PARAMETER);
//
// if (h != null)
// highlights.add(h);
}
super.visitParameterizedType(tree, null);
return null;
}
@Override
public Void visitBinary(BinaryTree tree, EnumSet<UseTypes> d) {
Tree left = tree.getLeftOperand();
Tree right = tree.getRightOperand();
if (left instanceof IdentifierTree) {
TreePath tp = new TreePath(getCurrentPath(), left);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.READ));
}
if (right instanceof IdentifierTree) {
TreePath tp = new TreePath(getCurrentPath(), right);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.READ));
}
super.visitBinary(tree, EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitClass(ClassTree tree, EnumSet<UseTypes> d) {
tl.moveToOffset(sourcePositions.getStartPosition(info.getCompilationUnit(), tree));
for (TypeParameterTree t : tree.getTypeParameters()) {
for (Tree bound : t.getBounds()) {
TreePath tp = new TreePath(new TreePath(getCurrentPath(), t), bound);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
}
}
if(getCurrentPath().getParentPath().getLeaf().getKind() != Kind.NEW_CLASS) {
//NEW_CLASS already handeled by visitnewClass
Tree extnds = tree.getExtendsClause();
if (extnds != null) {
TreePath tp = new TreePath(getCurrentPath(), extnds);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
}
for (Tree t : tree.getImplementsClause()) {
TreePath tp = new TreePath(getCurrentPath(), t);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
}
}
handlePossibleIdentifier(getCurrentPath(), EnumSet.of(UseTypes.DECLARATION));
scan(tree.getModifiers(), null);
// System.err.println("tree.getModifiers()=" + tree.getModifiers());
// System.err.println("mod end=" + sourcePositions.getEndPosition(info.getCompilationUnit(), tree.getModifiers()));
// System.err.println("class start=" + sourcePositions.getStartPosition(info.getCompilationUnit(), tree));
tl.moveToEnd(tree.getModifiers());
firstIdentifier(tree.getSimpleName().toString());
//XXX:????
scan(tree.getTypeParameters(), null);
scan(tree.getExtendsClause(), null);
scan(tree.getImplementsClause(), null);
ExecutableElement prevRecursionDetector = recursionDetector;
recursionDetector = null;
scan(tree.getMembers(), null);
recursionDetector = prevRecursionDetector;
//XXX: end ???
return null;
}
@Override
public Void visitUnary(UnaryTree tree, EnumSet<UseTypes> d) {
if (tree.getExpression() instanceof IdentifierTree) {
switch (tree.getKind()) {
case PREFIX_INCREMENT:
case PREFIX_DECREMENT:
case POSTFIX_INCREMENT:
case POSTFIX_DECREMENT:
Set<UseTypes> useTypes = EnumSet.of(UseTypes.WRITE);
if (d != null) {
useTypes.addAll(d);
}
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getExpression()), useTypes);
break;
default:
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getExpression()), EnumSet.of(UseTypes.READ));
}
}
super.visitUnary(tree, d);
return null;
}
@Override
public Void visitArrayAccess(ArrayAccessTree tree, EnumSet<UseTypes> d) {
scan(tree.getExpression(), EnumSet.of(UseTypes.READ));
scan(tree.getIndex(), EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitArrayType(ArrayTypeTree node, EnumSet<UseTypes> p) {
if (node.getType() != null) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), node.getType()), EnumSet.of(UseTypes.CLASS_USE));
}
return super.visitArrayType(node, p);
}
@Override
public Void visitUnionType(UnionTypeTree node, EnumSet<UseTypes> p) {
for (Tree tree : node.getTypeAlternatives()) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree), EnumSet.of(UseTypes.CLASS_USE));
}
return super.visitUnionType(node, p);
}
@Override
public Void visitNewArray(NewArrayTree tree, EnumSet<UseTypes> d) {
if (tree.getType() != null) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getType()), EnumSet.of(UseTypes.CLASS_USE));
}
scan(tree.getType(), null);
scan(tree.getDimensions(), EnumSet.of(UseTypes.READ));
scan(tree.getInitializers(), EnumSet.of(UseTypes.READ));
return null;
}
@Override
public Void visitCatch(CatchTree tree, EnumSet<UseTypes> d) {
scan(tree.getParameter(), EnumSet.of(UseTypes.WRITE));
scan(tree.getBlock(), null);
return null;
}
@Override
public Void visitConditionalExpression(ConditionalExpressionTree node, EnumSet<UseTypes> p) {
return super.visitConditionalExpression(node, EnumSet.of(UseTypes.READ));
}
@Override
public Void visitAssert(AssertTree tree, EnumSet<UseTypes> p) {
if (tree.getCondition().getKind() == Kind.IDENTIFIER)
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getCondition()), EnumSet.of(UseTypes.READ));
if (tree.getDetail() != null && tree.getDetail().getKind() == Kind.IDENTIFIER)
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getDetail()), EnumSet.of(UseTypes.READ));
return super.visitAssert(tree, EnumSet.of(UseTypes.READ));
}
@Override
public Void visitCase(CaseTree tree, EnumSet<UseTypes> p) {
if (tree.getExpression() != null && tree.getExpression().getKind() == Kind.IDENTIFIER) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getExpression()), EnumSet.of(UseTypes.READ));
}
return super.visitCase(tree, null);
}
@Override
public Void visitThrow(ThrowTree tree, EnumSet<UseTypes> p) {
if (tree.getExpression() != null && tree.getExpression().getKind() == Kind.IDENTIFIER) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), tree.getExpression()), EnumSet.of(UseTypes.READ));
}
return super.visitThrow(tree, p);
}
@Override
public Void visitTypeParameter(TypeParameterTree tree, EnumSet<UseTypes> p) {
for (Tree bound : tree.getBounds()) {
if (bound.getKind() == Kind.IDENTIFIER) {
TreePath tp = new TreePath(getCurrentPath(), bound);
handlePossibleIdentifier(tp, EnumSet.of(UseTypes.CLASS_USE));
}
}
return super.visitTypeParameter(tree, p);
}
@Override
public Void visitForLoop(ForLoopTree node, EnumSet<UseTypes> p) {
if (node.getCondition() != null && node.getCondition().getKind() == Kind.IDENTIFIER) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), node.getCondition()), EnumSet.of(UseTypes.READ));
}
return super.visitForLoop(node, p);
}
@Override
public Void visitWildcard(WildcardTree node, EnumSet<UseTypes> p) {
if (node.getBound() != null && node.getBound().getKind() == Kind.IDENTIFIER) {
handlePossibleIdentifier(new TreePath(getCurrentPath(), node.getBound()), EnumSet.of(UseTypes.CLASS_USE));
}
return super.visitWildcard(node, p);
}
}
public static interface ErrorDescriptionSetter {
public void setErrors(Document doc, List<ErrorDescription> errors, List<TreePathHandle> allUnusedImports);
public void setHighlights(Document doc, OffsetsBag highlights);
public void setColorings(Document doc, Map<Token, Coloring> colorings, Set<Token> addedTokens, Set<Token> removedTokens);
}
static ErrorDescriptionSetter ERROR_DESCRIPTION_SETTER = new ErrorDescriptionSetter() {
public void setErrors(Document doc, List<ErrorDescription> errors, List<TreePathHandle> allUnusedImports) {}
public void setHighlights(final Document doc, final OffsetsBag highlights) {
SwingUtilities.invokeLater(new Runnable() {
public void run() {
getImportHighlightsBag(doc).setHighlights(highlights);
}
});
}
public void setColorings(final Document doc, final Map<Token, Coloring> colorings, final Set<Token> addedTokens, final Set<Token> removedTokens) {
SwingUtilities.invokeLater(new Runnable () {
public void run() {
LexerBasedHighlightLayer.getLayer(SemanticHighlighter.class, doc).setColorings(colorings, addedTokens, removedTokens);
}
});
}
};
private static final Object KEY_UNUSED_IMPORTS = new Object();
static OffsetsBag getImportHighlightsBag(Document doc) {
OffsetsBag bag = (OffsetsBag) doc.getProperty(KEY_UNUSED_IMPORTS);
if (bag == null) {
doc.putProperty(KEY_UNUSED_IMPORTS, bag = new OffsetsBag(doc));
Object stream = doc.getProperty(Document.StreamDescriptionProperty);
if (stream instanceof DataObject) {
// TimesCollector.getDefault().reportReference(((DataObject) stream).getPrimaryFile(), "ImportsHighlightsBag", "[M] Imports Highlights Bag", bag);
}
}
return bag;
}
}