package com.redhat.ceylon.eclipse.code.search;
import static com.redhat.ceylon.eclipse.code.editor.Navigation.gotoLocation;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getPackage;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectModelLoader;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker;
import static com.redhat.ceylon.eclipse.util.JavaSearch.getCeylonSimpleName;
import static com.redhat.ceylon.eclipse.util.JavaSearch.getJavaQualifiedName;
import static com.redhat.ceylon.eclipse.util.JavaSearch.getProjectsToSearch;
import static com.redhat.ceylon.eclipse.util.JavaSearch.toCeylonDeclaration;
import static org.eclipse.jdt.core.IJavaElement.PACKAGE_FRAGMENT;
import static org.eclipse.jdt.core.IJavaElement.TYPE;
import static org.eclipse.jdt.core.search.IJavaSearchConstants.ALL_OCCURRENCES;
import static org.eclipse.jdt.core.search.IJavaSearchConstants.IMPLEMENTORS;
import static org.eclipse.jdt.core.search.IJavaSearchConstants.READ_ACCESSES;
import static org.eclipse.jdt.core.search.IJavaSearchConstants.REFERENCES;
import static org.eclipse.jdt.core.search.IJavaSearchConstants.WRITE_ACCESSES;
import static org.eclipse.search.ui.NewSearchUI.getSearchResultView;
import java.lang.reflect.Field;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IWorkspaceRoot;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Path;
import org.eclipse.jdt.core.IField;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jdt.core.IMember;
import org.eclipse.jdt.core.IMethod;
import org.eclipse.jdt.core.IPackageFragment;
import org.eclipse.jdt.core.IPackageFragmentRoot;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaCore;
import org.eclipse.jdt.ui.search.ElementQuerySpecification;
import org.eclipse.jdt.ui.search.IMatchPresentation;
import org.eclipse.jdt.ui.search.IQueryParticipant;
import org.eclipse.jdt.ui.search.ISearchRequestor;
import org.eclipse.jdt.ui.search.QuerySpecification;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.search.ui.text.AbstractTextSearchResult;
import org.eclipse.search.ui.text.AbstractTextSearchViewPage;
import org.eclipse.search.ui.text.Match;
import org.eclipse.ui.PartInitException;
import com.redhat.ceylon.compiler.typechecker.TypeChecker;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree.CompilationUnit;
import com.redhat.ceylon.eclipse.core.builder.CeylonNature;
import com.redhat.ceylon.ide.common.model.BaseIdeModule;
import com.redhat.ceylon.ide.common.util.FindAssignmentsVisitor;
import com.redhat.ceylon.ide.common.util.FindReferencesVisitor;
import com.redhat.ceylon.ide.common.util.FindSubtypesVisitor;
import com.redhat.ceylon.model.loader.ModelLoader;
import com.redhat.ceylon.model.typechecker.model.Declaration;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.Modules;
import com.redhat.ceylon.model.typechecker.model.Package;
import com.redhat.ceylon.model.typechecker.model.TypeDeclaration;
public class JavaQueryParticipant
implements IQueryParticipant, IMatchPresentation {
@Override
public void showMatch(Match match,
int offset, int length,
boolean activate)
throws PartInitException {
CeylonElement element =
(CeylonElement) match.getElement();
IFile file = element.getFile();
if (file==null) {
String path = element.getVirtualFile().getPath();
gotoLocation(new Path(path), offset, length);
}
else {
gotoLocation(file, offset, length);
}
}
@Override
public ILabelProvider createLabelProvider() {
AbstractTextSearchViewPage activePage =
(AbstractTextSearchViewPage)
getSearchResultView()
.getActivePage();
return new MatchCountingLabelProvider(activePage);
}
private static Field searchResultField;
private static Field participantsField;
static {
try {
Class<?> clazz = Class.forName("org.eclipse.jdt.internal.ui.search.JavaSearchQuery$SearchRequestor");
searchResultField = clazz.getDeclaredField("fSearchResult");
searchResultField.setAccessible(true);
clazz = Class.forName("org.eclipse.jdt.internal.ui.search.JavaSearchResult");
participantsField = clazz.getDeclaredField("fElementsToParticipants");
participantsField.setAccessible(true);
}
catch (Exception e) {
e.printStackTrace();
}
}
@Override
public void search(ISearchRequestor requestor,
QuerySpecification querySpecification,
IProgressMonitor monitor)
throws CoreException {
if (querySpecification
instanceof ElementQuerySpecification) {
ElementQuerySpecification eqs =
(ElementQuerySpecification)
querySpecification;
final IJavaElement element =
eqs.getElement();
if (!(element instanceof IType ||
element instanceof IMethod ||
element instanceof IField) ||
element.getJavaProject()==null) {
return;
}
int limitTo = querySpecification.getLimitTo();
if (limitTo!=REFERENCES &&
limitTo!=IMPLEMENTORS &&
limitTo!=ALL_OCCURRENCES &&
limitTo!=READ_ACCESSES &&
limitTo!=WRITE_ACCESSES) {
return;
}
IProject elementProject =
element.getJavaProject()
.getProject();
IPackageFragment packageFragment =
(IPackageFragment)
element.getAncestor(PACKAGE_FRAGMENT);
Package pack =
packageFragment==null ? null :
getPackage(packageFragment);
Declaration declaration;
if (pack==null) {
//this is the case for Ceylon decs, since
//they sit in the .exploded directory
declaration =
toCeylonDeclaration(elementProject,
element);
if (declaration!=null) {
pack = declaration.getUnit().getPackage();
}
}
else {
//this is the case for Java decs
IType type =
(IType)
element.getAncestor(TYPE);
String typeQualifiedName =
getJavaQualifiedName(type);
String ceylonMemberName =
type==element ? null :
getCeylonSimpleName(
(IMember) element);
String typeName = type.getElementName();
int first = typeName.codePointAt(0);
if (!Character.isUpperCase(first)
&& typeName.endsWith("_")
&& ceylonMemberName == null) {
// Ceylon object value ...
// ... without a method call
declaration =
getProjectModelLoader(elementProject)
.convertToDeclaration(
pack.getModule(),
typeQualifiedName,
ModelLoader.DeclarationType.VALUE);
} else {
declaration =
getProjectModelLoader(elementProject)
.convertToDeclaration(
pack.getModule(),
typeQualifiedName,
ModelLoader.DeclarationType.TYPE);
if (declaration!=null
&& ceylonMemberName != null
&& element instanceof IMember) {
declaration =
declaration.getMember(
ceylonMemberName,
null, false);
}
}
}
if (declaration==null) return;
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
Set<String> searchedArchives =
new HashSet<String>();
IWorkspaceRoot root =
elementProject.getWorkspace()
.getRoot();
for (IPath includedProjectOrJar:
querySpecification.getScope()
.enclosingProjectsAndJars()) {
IProject project = null;
if (includedProjectOrJar.segmentCount()==1) {
String prefix =
includedProjectOrJar.segment(0);
project = root.getProject(prefix);
if (!project.exists()) {
project = null;
}
}
if (project == null) {
continue;
}
for (IProject searchedProject:
getProjectsToSearch(project)) {
if (CeylonNature.isEnabled(searchedProject)) {
IJavaProject javaProject =
JavaCore.create(searchedProject);
for (IPackageFragmentRoot sourceFolder:
javaProject.getAllPackageFragmentRoots()) {
if (sourceFolder.getKind()==
IPackageFragmentRoot.K_SOURCE &&
querySpecification.getScope()
.encloses(sourceFolder)) {
TypeChecker typeChecker =
getProjectTypeChecker(
searchedProject);
searchInUnits(requestor,
limitTo, declaration,
typeChecker.getPhasedUnits()
.getPhasedUnits());
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
Modules modules =
typeChecker.getContext()
.getModules();
for (Module mod:
modules.getListOfModules()) {
if (mod instanceof BaseIdeModule) {
BaseIdeModule module =
(BaseIdeModule) mod;
if (module.getIsCeylonArchive() &&
module.getArtifact()!=null) {
String archivePath =
module.getArtifact()
.getAbsolutePath();
if (searchedArchives.add(archivePath) &&
mod.getAllReachablePackages()
.contains(pack)) {
searchInUnits(requestor,
limitTo, declaration,
module.getPhasedUnitsAsJavaList());
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}
}
break;
}
}
}
}
}
}
}
private void searchInUnits(ISearchRequestor requestor,
int limitTo,
Declaration declaration,
List<? extends PhasedUnit> units) {
for (PhasedUnit pu: units) {
CompilationUnit cu = pu.getCompilationUnit();
for (Node node:
findNodes(limitTo, declaration, cu)) {
if (node.getToken()==null) {
//a synthetic node inserted in the tree
}
else {
CeylonSearchMatch match =
CeylonSearchMatch.create(node,
cu, pu.getUnitFile());
if (searchResultField!=null &&
participantsField!=null) {
//nasty nasty workaround for stupid
//behavior of reportMatch()
try {
reportMatchBypassingRubbishApi(
requestor, match);
continue;
}
catch (Exception e) {
e.printStackTrace();
}
}
requestor.reportMatch(match);
}
}
}
}
private void reportMatchBypassingRubbishApi(
ISearchRequestor requestor,
CeylonSearchMatch match)
throws IllegalAccessException {
AbstractTextSearchResult searchResult =
(AbstractTextSearchResult)
searchResultField.get(requestor);
searchResult.addMatch(match);
@SuppressWarnings("unchecked")
Map<Object, IMatchPresentation> participants =
(Map<Object, IMatchPresentation>)
participantsField.get(searchResult);
participants.put(match.getElement(), this);
}
private Set<? extends Node> findNodes(int limitTo,
Declaration declaration,
CompilationUnit cu) {
Set<? extends Node> nodes;
if (limitTo==WRITE_ACCESSES) {
FindAssignmentsVisitor fav =
new FindAssignmentsVisitor(declaration);
fav.visit(cu);
nodes = fav.getAssignmentNodeSet();
}
else if (limitTo==IMPLEMENTORS) {
FindSubtypesVisitor fsv =
new FindSubtypesVisitor(
(TypeDeclaration)
declaration);
fsv.visit(cu);
nodes = fsv.getDeclarationNodeSet();
}
else if (limitTo==REFERENCES ||
limitTo==READ_ACCESSES) { //TODO: support this properly!!
FindReferencesVisitor frv =
new FindReferencesVisitor(declaration);
frv.visit(cu);
nodes = frv.getReferenceNodeSet();
}
else {
//ALL_OCCURRENCES
FindReferencesVisitor frv =
new FindReferencesVisitor(declaration);
frv.visit(cu);
nodes = frv.getReferenceNodeSet();
if (declaration instanceof TypeDeclaration) {
FindSubtypesVisitor fsv =
new FindSubtypesVisitor(
(TypeDeclaration)
declaration);
fsv.visit(cu);
HashSet<Node> result = new HashSet<Node>();
result.addAll(nodes);
result.addAll(fsv.getDeclarationNodeSet());
nodes = result;
}
}
return nodes;
}
@Override
public int estimateTicks(QuerySpecification specification) {
//TODO!
return 1;
}
@Override
public IMatchPresentation getUIParticipant() {
return this;
}
}