/* * Copyright 2009-2017 the original author or authors. * * Licensed 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.eclipse.jdt.groovy.search; import java.util.HashSet; import java.util.Set; import org.codehaus.groovy.ast.ASTNode; import org.codehaus.groovy.ast.ClassNode; import org.codehaus.groovy.ast.FieldNode; import org.codehaus.groovy.ast.expr.ConstantExpression; import org.codehaus.groovy.ast.expr.FieldExpression; import org.codehaus.groovy.ast.expr.VariableExpression; import org.codehaus.jdt.groovy.model.GroovyClassFileWorkingCopy; import org.eclipse.core.runtime.CoreException; import org.eclipse.jdt.core.IJavaElement; import org.eclipse.jdt.core.compiler.CharOperation; import org.eclipse.jdt.core.search.FieldDeclarationMatch; import org.eclipse.jdt.core.search.FieldReferenceMatch; import org.eclipse.jdt.core.search.SearchMatch; import org.eclipse.jdt.core.search.SearchParticipant; import org.eclipse.jdt.core.search.SearchRequestor; import org.eclipse.jdt.groovy.core.util.GroovyUtils; import org.eclipse.jdt.groovy.core.util.ReflectionUtils; import org.eclipse.jdt.groovy.search.TypeLookupResult.TypeConfidence; import org.eclipse.jdt.internal.core.search.matching.FieldPattern; import org.eclipse.jdt.internal.core.search.matching.VariablePattern; import org.eclipse.jdt.internal.core.util.Util; import org.eclipse.jface.text.Position; public class FieldReferenceSearchRequestor implements ITypeRequestor { private final SearchRequestor requestor; private final SearchParticipant participant; private final char[] name; private final String declaringQualifiedName; private final boolean readAccess; private final boolean writeAccess; private final boolean findDeclarations; private final boolean findReferences; private final Set<Position> acceptedPositions = new HashSet<Position>(); public FieldReferenceSearchRequestor(FieldPattern pattern, SearchRequestor requestor, SearchParticipant participant) { this.requestor = requestor; this.participant = participant; name = (char[]) ReflectionUtils.getPrivateField(VariablePattern.class, "name", pattern); char[] arr = (char[]) ReflectionUtils.getPrivateField(FieldPattern.class, "declaringSimpleName", pattern); String declaringSimpleName = arr == null ? "" : String.valueOf(arr); arr = (char[]) ReflectionUtils.getPrivateField(FieldPattern.class, "declaringQualification", pattern); String declaringQualification = ((arr == null || arr.length == 0) ? "" : (String.valueOf(arr) + ".")); declaringQualifiedName = declaringQualification + declaringSimpleName; readAccess = (Boolean) ReflectionUtils.getPrivateField(VariablePattern.class, "readAccess", pattern); writeAccess = (Boolean) ReflectionUtils.getPrivateField(VariablePattern.class, "writeAccess", pattern); findDeclarations = (Boolean) ReflectionUtils.getPrivateField(VariablePattern.class, "findDeclarations", pattern); findReferences = (Boolean) ReflectionUtils.getPrivateField(VariablePattern.class, "findReferences", pattern); } public VisitStatus acceptASTNode(ASTNode node, TypeLookupResult result, IJavaElement enclosingElement) { boolean doCheck = false; boolean isAssignment = false; boolean isDeclaration = false; int start = 0; int end = 0; // include method calls here because of closures if (node instanceof ConstantExpression) { String cName = ((ConstantExpression) node).getText(); if (cName != null && CharOperation.equals(name, cName.toCharArray())) { doCheck = true; if (EqualityVisitor.checkForAssignment(node, result.enclosingAssignment)) { isAssignment = true; } start = node.getStart(); end = node.getEnd(); } } else if (node instanceof FieldExpression) { if (CharOperation.equals(name, ((FieldExpression) node).getFieldName().toCharArray())) { doCheck = true; if (EqualityVisitor.checkForAssignment(node, result.enclosingAssignment)) { isAssignment = true; } // fully qualified field expressions in static contexts will have an sloc of the entire qualified name end = node.getEnd(); start = end - name.length; } } else if (node instanceof FieldNode) { FieldNode fnode = (FieldNode) node; if (CharOperation.equals(name, fnode.getName().toCharArray())) { doCheck = true; isDeclaration = true; // assume all fieldNodes are assignments. Not true if there is no initializer, but we can't know this at this point // since the initializer has already been moved to the <init> isAssignment = true; start = fnode.getNameStart(); end = fnode.getNameEnd() + 1; // arrrgh...why +1? } } else if (node instanceof VariableExpression) { VariableExpression vnode = (VariableExpression) node; if (CharOperation.equals(name, vnode.getName().toCharArray())) { doCheck = true; if (EqualityVisitor.checkForAssignment(node, result.enclosingAssignment)) { isAssignment = true; } start = vnode.getStart(); end = start + vnode.getName().length(); } } if (doCheck && end > 0 && result.declaringType != null) { // don't want to double accept nodes; this could happen with field and object initializers can get pushed into multiple constructors Position position = new Position(start, end - start); if (!acceptedPositions.contains(position)) { boolean isCompleteMatch = qualifiedNameMatches(GroovyUtils.getBaseType(result.declaringType)); // GRECLIPSE-540: Still unresolved is that all field and variable references are considered reads. We don't know about writes. if (isCompleteMatch && ((isAssignment && writeAccess) || (!isAssignment && readAccess) || (isDeclaration && findDeclarations))) { SearchMatch match = null; // must translate from synthetic source to binary if necessary IJavaElement realElement = enclosingElement.getOpenable() instanceof GroovyClassFileWorkingCopy ? ((GroovyClassFileWorkingCopy) enclosingElement.getOpenable()).convertToBinary(enclosingElement) : enclosingElement; if (isDeclaration && findDeclarations) { match = new FieldDeclarationMatch(realElement, getAccuracy(result.confidence, isCompleteMatch), start, end - start, participant, realElement.getResource()); } else if (!isDeclaration && findReferences) { match = new FieldReferenceMatch(realElement, getAccuracy(result.confidence, isCompleteMatch), start, end - start, !isAssignment, isAssignment, false, participant, realElement.getResource()); } if (match != null) { try { requestor.acceptSearchMatch(match); acceptedPositions.add(position); } catch (CoreException e) { Util.log(e, "Error reporting search match inside of " + realElement + " in resource " + realElement.getResource()); } } } } } return VisitStatus.CONTINUE; } // recursively check the hierarchy private boolean qualifiedNameMatches(ClassNode declaringType) { if (declaringType == null) { // no declaring type; probably a variable declaration return false; } else if (declaringQualifiedName == null || declaringQualifiedName.equals("")) { // no type specified, accept all return true; } else if (declaringType.getName().equals(declaringQualifiedName)) { return true; } else { return false; } } private int getAccuracy(TypeConfidence confidence, boolean isCompleteMatch) { if (shouldAlwaysBeAccurate()) { return SearchMatch.A_ACCURATE; } if (!isCompleteMatch) { return SearchMatch.A_INACCURATE; } switch (confidence) { case EXACT: return SearchMatch.A_ACCURATE; default: return SearchMatch.A_INACCURATE; } } /** * Checks to see if this requestor has something to do with refactoring. * If so, we always want an accurate match otherwise we get complaints * in the refactoring wizard of "possible matches". */ private boolean shouldAlwaysBeAccurate() { return requestor.getClass().getPackage().getName().indexOf("refactoring") != -1; } }