package com.redhat.ceylon.eclipse.code.search;
import static com.redhat.ceylon.eclipse.core.builder.CeylonBuilder.getProjectTypeChecker;
import static com.redhat.ceylon.eclipse.java2ceylon.Java2CeylonProxies.vfsJ2C;
import static com.redhat.ceylon.eclipse.util.EditorUtil.getActivePage;
import static com.redhat.ceylon.eclipse.util.InteropUtils.toJavaString;
import static java.util.regex.Pattern.CASE_INSENSITIVE;
import static java.util.regex.Pattern.compile;
import static org.eclipse.core.resources.ResourcesPlugin.getWorkspace;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.regex.Pattern;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.OperationCanceledException;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.text.IRegion;
import org.eclipse.search.ui.ISearchQuery;
import org.eclipse.search.ui.ISearchResult;
import org.eclipse.search.ui.text.AbstractTextSearchResult;
import org.eclipse.ui.IEditorPart;
import org.eclipse.ui.IWorkbenchPage;
import com.redhat.ceylon.compiler.typechecker.TypeChecker;
import com.redhat.ceylon.compiler.typechecker.context.PhasedUnit;
import com.redhat.ceylon.compiler.typechecker.io.VirtualFile;
import com.redhat.ceylon.compiler.typechecker.tree.Node;
import com.redhat.ceylon.compiler.typechecker.tree.Tree;
import com.redhat.ceylon.eclipse.code.editor.CeylonEditor;
import com.redhat.ceylon.eclipse.code.parse.CeylonParseController;
import com.redhat.ceylon.eclipse.core.builder.CeylonBuilder;
import com.redhat.ceylon.eclipse.core.builder.CeylonNature;
import com.redhat.ceylon.eclipse.util.Filters;
import com.redhat.ceylon.eclipse.util.SearchVisitor;
import com.redhat.ceylon.ide.common.model.BaseIdeModule;
import com.redhat.ceylon.ide.common.model.CeylonProject;
import com.redhat.ceylon.ide.common.model.IdeModule;
import com.redhat.ceylon.model.typechecker.model.Module;
import com.redhat.ceylon.model.typechecker.model.Modules;
class CeylonSearchQuery implements ISearchQuery {
class PatternMatcher implements SearchVisitor.Matcher {
Pattern pattern = compile(patternString(), flags());
private String patternString() {
return regex ? string : string
.replace("*", ".*").replace("?", ".");
}
private int flags() {
return caseSensitive ? 0 : CASE_INSENSITIVE;
}
@Override
public boolean matches(String string) {
return pattern.matcher(string).matches();
}
@Override
public boolean includeReferences() {
return includeReferences;
}
@Override
public boolean includeDeclarations() {
return includeDeclarations;
}
@Override
public boolean includeTypes() {
return includeTypes;
}
@Override
public boolean includeImports() {
return includeImports;
}
@Override
public boolean includeDoc() {
return includeDoc;
}
@Override
public int kinds() {
return kinds;
}
}
private AbstractTextSearchResult result =
new CeylonSearchResult(this);
private final String string;
private final String[] projects;
private final IResource[] resources;
private int count = 0;
private final boolean caseSensitive;
private final boolean regex;
private final boolean includeReferences;
private final boolean includeTypes;
private final boolean includeDeclarations;
private final boolean includeImports;
private final boolean includeDoc;
private final boolean sources;
private final boolean archives;
private final int kinds;
private IWorkbenchPage page;
CeylonSearchQuery(String string,
String[] projects, IResource[] resources,
boolean includeReferences,
boolean includeTypes,
boolean includeDeclarations,
boolean includeImports,
boolean includeDoc,
boolean caseSensitive, boolean regex,
boolean sources, boolean archives,
int kinds) {
this.string = string;
this.projects = projects;
this.includeTypes = includeTypes;
this.includeImports = includeImports;
this.includeDoc = includeDoc;
this.caseSensitive = caseSensitive;
this.includeDeclarations = includeDeclarations;
this.includeReferences = includeReferences;
this.regex = regex;
this.resources = resources;
this.sources = sources;
this.archives = archives;
this.kinds = kinds;
this.page = getActivePage();
}
private Collection<IProject> getProjectsToSearch() {
if (projects==null) {
return CeylonBuilder.getProjects();
}
List<IProject> result =
new ArrayList<IProject>();
for (String name: projects) {
IProject project =
getWorkspace()
.getRoot()
.getProject(name);
if (project!=null) {
// if (CeylonNature.isEnabled(project)) {
result.add(project);
// }
}
}
return result;
}
@Override
public IStatus run(IProgressMonitor monitor)
throws OperationCanceledException {
monitor.beginTask("Searching in Ceylon source",
estimateWork(monitor));
if (monitor.isCanceled()) {
return Status.CANCEL_STATUS;
}
search(monitor);
monitor.done();
return Status.OK_STATUS;
}
private Filters filters = new Filters();
private void search(IProgressMonitor monitor) {
result.removeAll();
count = 0;
Set<String> searchedArchives = new HashSet<String>();
Collection<IProject> projectsToSearch = getProjectsToSearch();
for (IProject project: projectsToSearch) {
if (CeylonNature.isEnabled(project)) {
TypeChecker typeChecker =
getProjectTypeChecker(project);
if (sources) {
List<PhasedUnit> phasedUnits =
typeChecker.getPhasedUnits()
.getPhasedUnits();
findInUnits(monitor, phasedUnits);
monitor.worked(1);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
if (archives) {
Modules modules =
typeChecker.getContext()
.getModules();
for (Module m: modules.getListOfModules()) {
if (m instanceof IdeModule &&
!filters.isFiltered(m)) {
IdeModule module = (IdeModule) m;
if (module.getIsCeylonArchive() &&
!module.getIsProjectModule() &&
module.getArtifact()!=null) {
CeylonProject originalProject =
module.getOriginalProject();
if (originalProject != null
&& projectsToSearch.contains(
originalProject.getIdeArtifact())) {
continue;
}
String archivePath =
module.getArtifact()
.getAbsolutePath();
String sourceArchivePath =
toJavaString(module.getSourceArchivePath());
if (searchedArchives.add(archivePath) &&
searchedArchives.add(sourceArchivePath)) {
findInUnits(monitor,
module.getPhasedUnitsAsJavaList());
monitor.worked(1);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}
}
}
}
}
}
private void findInUnits(IProgressMonitor monitor,
List<? extends PhasedUnit> units) {
for (final PhasedUnit pu: units) {
if (isWithinSelection(pu)) {
final Tree.CompilationUnit rootNode =
getRootNode(pu);
monitor.subTask("Searching source file " +
pu.getUnitFile().getPath());
SearchVisitor sv =
new SearchVisitor(
new PatternMatcher()) {
@Override
public void matchingNode(Node node) {
CeylonSearchMatch match =
CeylonSearchMatch.create(
node, rootNode,
pu.getUnitFile());
result.addMatch(match);
count++;
}
@Override
public void matchingRegion(Node node,
IRegion region) {
CeylonSearchMatch match =
CeylonSearchMatch.create(
node, rootNode,
pu.getUnitFile());
match.setOffset(region.getOffset());
match.setLength(region.getLength());
result.addMatch(match);
}
};
rootNode.visit(sv);
if (monitor.isCanceled()) {
throw new OperationCanceledException();
}
}
}
}
private int estimateWork(IProgressMonitor monitor) {
int work = 0;
Set<String> searchedArchives = new HashSet<String>();
for (IProject project: getProjectsToSearch()) {
if (CeylonNature.isEnabled(project)) {
if (sources) {
work++;
}
if (archives) {
Modules modules =
getProjectTypeChecker(project)
.getContext()
.getModules();
for (Module m: modules.getListOfModules()) {
if (m instanceof BaseIdeModule) {
BaseIdeModule module = (BaseIdeModule) m;
if (module.getIsCeylonArchive() &&
!module.getIsProjectModule() &&
module.getArtifact()!=null) {
String archivePath =
module.getArtifact()
.getAbsolutePath();
String sourceArchivePath =
toJavaString(module.getSourceArchivePath());
if (searchedArchives.add(archivePath) &&
searchedArchives.add(sourceArchivePath)) {
work++;
}
}
}
}
}
}
}
return work;
}
//TODO: copy/pasted from FindSearchQuery!
Tree.CompilationUnit getRootNode(PhasedUnit pu) {
for (IEditorPart editor: page.getDirtyEditors()) {
if (editor instanceof CeylonEditor) {
CeylonEditor ce = (CeylonEditor) editor;
CeylonParseController cpc =
ce.getParseController();
if ( /*editor.isDirty() &&*/
pu.getUnit().equals(cpc.getLastCompilationUnit().getUnit()) ) {
return cpc.getLastCompilationUnit();
}
}
}
return pu.getCompilationUnit();
}
public boolean isWithinSelection(PhasedUnit pu) {
if (filters.isFiltered(pu.getPackage())) {
return false;
}
if (resources==null) {
return true;
}
else {
for (IResource r: resources) {
VirtualFile unitFile = pu.getUnitFile();
if (vfsJ2C().instanceOfIFileVirtualFile(unitFile)) {
IPath loc =
vfsJ2C().getIFileVirtualFile(unitFile).getNativeResource().getLocation();
if (r.getLocation().isPrefixOf(loc)) {
return true;
}
}
}
return false;
}
}
@Override
public ISearchResult getSearchResult() {
return result;
}
@Override
public String getLabel() {
String label = "Displaying " + count +
" matches of '" + string + "'";
if (projects!=null && projects.length!=0) {
label += " in project ";
for (String project: projects) {
label += project + ", ";
}
label = label.substring(0, label.length()-2);
}
return label;
}
@Override
public boolean canRunInBackground() {
return true;
}
@Override
public boolean canRerun() {
return true;
}
}