/*
* Copyright 2009-2016 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.codehaus.groovy.eclipse.core.search;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.jdt.groovy.model.GroovyNature;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.jobs.ISchedulingRule;
import org.eclipse.jdt.core.Flags;
import org.eclipse.jdt.core.IAnnotatable;
import org.eclipse.jdt.core.IAnnotation;
import org.eclipse.jdt.core.IClassFile;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaModel;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.ILocalVariable;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMemberValuePair;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IOpenable;
import org.eclipse.jdt.core.ISourceRange;
import org.eclipse.jdt.core.ISourceReference;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.ITypeParameter;
import org.eclipse.jdt.core.ITypeRoot;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.core.search.IJavaSearchConstants;
import org.eclipse.jdt.core.search.IJavaSearchScope;
import org.eclipse.jdt.core.search.SearchEngine;
import org.eclipse.jdt.core.search.SearchMatch;
import org.eclipse.jdt.core.search.SearchParticipant;
import org.eclipse.jdt.core.search.SearchPattern;
import org.eclipse.jdt.core.search.SearchRequestor;
import org.eclipse.jdt.internal.core.search.JavaSearchParticipant;
/**
* Search requestor that finds synthetic accessors
*
* @author andrew
* @created Oct 3, 2011
*/
public class SyntheticAccessorSearchRequestor {
/**
* This class delegates to an actual {@link IMethod} for most calls
* It also ensures that {@link JavaModelException}s are not thrown
* unless an unsupported operation is called.
*
* @author andrew
* @created Oct 5, 2011
*/
public class MethodWrapper implements IMethod {
private final IMethod delegate;
private final String[] parameterNames;
public MethodWrapper(IMethod method, String[] parameterNames) {
delegate = method;
this.parameterNames = parameterNames;
}
public String[] getCategories() throws JavaModelException {
return ((IMember) delegate.getParent()).getCategories();
}
public IClassFile getClassFile() {
return delegate.getClassFile();
}
public ICompilationUnit getCompilationUnit() {
return delegate.getCompilationUnit();
}
public IType getDeclaringType() {
return delegate.getDeclaringType();
}
public int getFlags() throws JavaModelException {
return ((IMember) delegate.getParent()).getFlags();
}
public ISourceRange getJavadocRange() throws JavaModelException {
return ((IMember) delegate.getParent()).getJavadocRange();
}
public int getOccurrenceCount() {
return delegate.getOccurrenceCount();
}
public ITypeRoot getTypeRoot() {
return delegate.getTypeRoot();
}
public IType getType(String name, int occurrenceCount) {
return delegate.getType(name, occurrenceCount);
}
public boolean isBinary() {
return delegate.isBinary();
}
// synthetic method, but pretend that it exists
public boolean exists() {
return true;
}
public IJavaElement getAncestor(int ancestorType) {
return delegate.getAncestor(ancestorType);
}
public String getAttachedJavadoc(IProgressMonitor monitor) throws JavaModelException {
return delegate.getParent().getAttachedJavadoc(monitor);
}
public IResource getCorrespondingResource() throws JavaModelException {
return delegate.getParent().getCorrespondingResource();
}
public int getElementType() {
return delegate.getElementType();
}
public String getHandleIdentifier() {
return delegate.getHandleIdentifier();
}
public IJavaModel getJavaModel() {
return delegate.getJavaModel();
}
public IJavaProject getJavaProject() {
return delegate.getJavaProject();
}
public IOpenable getOpenable() {
return delegate.getOpenable();
}
public IJavaElement getParent() {
return delegate.getParent();
}
public IPath getPath() {
return delegate.getPath();
}
public IJavaElement getPrimaryElement() {
return delegate.getPrimaryElement();
}
public IResource getResource() {
return delegate.getResource();
}
public ISchedulingRule getSchedulingRule() {
return delegate.getSchedulingRule();
}
public IResource getUnderlyingResource() throws JavaModelException {
return delegate.getParent().getUnderlyingResource();
}
public boolean isReadOnly() {
return delegate.isReadOnly();
}
public boolean isStructureKnown() throws JavaModelException {
return delegate.getParent().isStructureKnown();
}
@SuppressWarnings({"rawtypes", "unchecked"})
public Object getAdapter(Class adapter) {
return delegate.getAdapter(adapter);
}
public String getSource() throws JavaModelException {
return ((ISourceReference) delegate.getParent()).getSource();
}
public ISourceRange getSourceRange() throws JavaModelException {
return ((ISourceReference) delegate.getParent()).getSourceRange();
}
public ISourceRange getNameRange() throws JavaModelException {
return ((IMethod) delegate.getParent()).getNameRange();
}
public void copy(IJavaElement container, IJavaElement sibling, String rename, boolean replace, IProgressMonitor monitor)
throws JavaModelException {
// will throw exception
delegate.copy(container, sibling, rename, replace, monitor);
}
public void delete(boolean force, IProgressMonitor monitor) throws JavaModelException {
// will throw exception
delegate.delete(force, monitor);
}
public void move(IJavaElement container, IJavaElement sibling, String rename, boolean replace, IProgressMonitor monitor)
throws JavaModelException {
// will throw exception
delegate.move(container, sibling, rename, replace, monitor);
}
public void rename(String name, boolean replace, IProgressMonitor monitor) throws JavaModelException {
// will throw exception
delegate.rename(name, replace, monitor);
}
public IJavaElement[] getChildren() throws JavaModelException {
return new IJavaElement[0];
}
public boolean hasChildren() throws JavaModelException {
return false;
}
public IAnnotation getAnnotation(String name) {
return delegate.getAnnotation(name);
}
public IAnnotation[] getAnnotations() throws JavaModelException {
return ((IAnnotatable) delegate.getParent()).getAnnotations();
}
public IMemberValuePair getDefaultValue() throws JavaModelException {
return null;
}
public String getElementName() {
return delegate.getElementName();
}
public String[] getExceptionTypes() throws JavaModelException {
return new String[0];
}
public String[] getTypeParameterSignatures() throws JavaModelException {
return new String[0];
}
public ITypeParameter[] getTypeParameters() throws JavaModelException {
return new ITypeParameter[0];
}
public int getNumberOfParameters() {
return delegate.getNumberOfParameters();
}
public ILocalVariable[] getParameters() throws JavaModelException {
return new ILocalVariable[0];
}
public String getKey() {
return delegate.getKey();
}
public String[] getParameterNames() throws JavaModelException {
return parameterNames;
}
public String[] getParameterTypes() {
return delegate.getParameterTypes();
}
public String[] getRawParameterNames() throws JavaModelException {
return parameterNames;
}
public String getReturnType() throws JavaModelException {
// will throw exception
return delegate.getReturnType();
}
public String getSignature() throws JavaModelException {
// will throw exception
return delegate.getSignature();
}
public ITypeParameter getTypeParameter(String name) {
return delegate.getTypeParameter(name);
}
public boolean isConstructor() throws JavaModelException {
return false;
}
public boolean isMainMethod() throws JavaModelException {
return false;
}
public boolean isResolved() {
return false;
}
public boolean isSimilar(IMethod method) {
return delegate.isSimilar(method);
}
public boolean reallyExists() {
return delegate.exists();
}
public boolean isLambdaMethod() {
// don't do this right now as it won't be in all versions of IMethod
// return delegate.isLambdaMethod();
return false;
}
}
private class Requestor extends SearchRequestor {
private final ISearchRequestor uiRequestor;
public Requestor(ISearchRequestor uiRequestor) {
this.uiRequestor = uiRequestor;
}
@Override
public void acceptSearchMatch(SearchMatch match) throws CoreException {
uiRequestor.acceptMatch(match);
}
}
public void findSyntheticMatches(IJavaElement element, ISearchRequestor uiRequestor, IProgressMonitor monitor)
throws CoreException {
// findSyntheticMatches(element, IJavaSearchConstants.REFERENCES, new
// SearchParticipant[] { new JavaSearchParticipant() },
// SearchEngine.createJavaSearchScope(new IJavaElement[] { element }),
// uiRequestor, monitor);
findSyntheticMatches(element, IJavaSearchConstants.REFERENCES, new SearchParticipant[] { new JavaSearchParticipant() },
SearchEngine.createWorkspaceScope(), uiRequestor, monitor);
}
public void findSyntheticMatches(IJavaElement element, int limitTo, SearchParticipant[] participants,
IJavaSearchScope scope, ISearchRequestor uiRequestor, IProgressMonitor monitor) throws CoreException {
if (!isInteresting(element)) {
return;
}
// the declaration is synthetic, so OK to ignore
if (limitTo == IJavaSearchConstants.DECLARATIONS) {
return;
}
SearchPattern pattern = createPattern(element);
if (pattern == null) {
return;
}
Requestor requestor = new Requestor(uiRequestor);
SearchEngine engine = new SearchEngine();
engine.search(pattern, participants, scope, requestor, monitor);
}
private SearchPattern createPattern(IJavaElement element) throws JavaModelException {
List<IJavaElement> toSearch = new ArrayList<IJavaElement>(4);
toSearch.add(findSyntheticMember(element, "is"));
toSearch.add(findSyntheticMember(element, "get"));
toSearch.add(findSyntheticMember(element, "set"));
toSearch.add(findSyntheticProperty(element));
SearchPattern pattern = null;
for (IJavaElement searchElt : toSearch) {
if (searchElt != null) {
SearchPattern newPattern = SearchPattern.createPattern(searchElt, IJavaSearchConstants.ALL_OCCURRENCES
| IJavaSearchConstants.IGNORE_RETURN_TYPE);
if (pattern == null) {
pattern = newPattern;
} else {
pattern = SearchPattern.createOrPattern(pattern, newPattern);
}
}
}
return pattern;
}
private boolean isInteresting(IJavaElement element) {
return element instanceof IMember && GroovyNature.hasGroovyNature(element.getJavaProject().getProject());
}
private IMethod findSyntheticMember(IJavaElement element, String prefix) throws JavaModelException {
if (element.getElementType() != IJavaElement.FIELD) {
return null;
}
IType parent = (IType) element.getParent();
String[] sigs;
String[] names;
if (prefix.equals("set")) {
sigs = new String[] { ((IField) element).getTypeSignature() };
names = new String[] { element.getElementName() };
} else {
sigs = new String[0];
names = new String[0];
}
MethodWrapper method = new MethodWrapper(parent.getMethod(convertName(prefix, element.getElementName()), sigs), names);
// only return if method doesn't exist since otherwise, this method would not be synthetic
return method.reallyExists() ? null : method;
}
private IField findSyntheticProperty(IJavaElement element) throws JavaModelException {
if (element.getElementType() != IJavaElement.METHOD) {
return null;
}
String name = element.getElementName();
if (name.length() <= 2) {
return null;
}
int prefixLength;
if (name.startsWith("is")) {
prefixLength = 2;
} else {
if (name.length() == 3) {
return null;
}
prefixLength = 3;
}
String fieldName = "" + Character.toLowerCase(name.charAt(prefixLength)) + name.substring(prefixLength+1);
IType parent = (IType) element.getParent();
IField field = parent.getField(fieldName);
// only return if field doesn't exist since otherwise, this method would
// not be synthetic
return field.exists() && Flags.isProtected(field.getFlags()) ? null : field;
}
private String convertName(String prefix, String elementName) {
return prefix + Character.toUpperCase(elementName.charAt(0)) + elementName.subSequence(1, elementName.length());
}
}