/*******************************************************************************
* Copyright (c) 2005 - 2007 committers of openArchitectureWare and others. All
* rights reserved. This program and the accompanying materials are made
* available under the terms of the Eclipse Public License v1.0 which
* accompanies this distribution, and is available at
* http://www.eclipse.org/legal/epl-v10.html
*
* Contributors: committers of openArchitectureWare - initial API and
* implementation
******************************************************************************/
package org.eclipse.xtend.shared.ui.core.search;
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.internal.xpand2.XpandUtil;
import org.eclipse.internal.xpand2.ast.AbstractDefinition;
import org.eclipse.internal.xpand2.ast.AbstractXpandVisitor;
import org.eclipse.internal.xpand2.ast.Definition;
import org.eclipse.internal.xpand2.ast.ErrorStatement;
import org.eclipse.internal.xpand2.ast.ExpandStatement;
import org.eclipse.internal.xpand2.ast.ExpressionStatement;
import org.eclipse.internal.xpand2.ast.FileStatement;
import org.eclipse.internal.xpand2.ast.ForEachStatement;
import org.eclipse.internal.xpand2.ast.IfStatement;
import org.eclipse.internal.xpand2.ast.LetStatement;
import org.eclipse.internal.xpand2.ast.ProtectStatement;
import org.eclipse.internal.xpand2.ast.Template;
import org.eclipse.internal.xpand2.model.XpandDefinition;
import org.eclipse.internal.xpand2.model.XpandResource;
import org.eclipse.internal.xtend.expression.ast.AbstractExpressionVisitor;
import org.eclipse.internal.xtend.expression.ast.Expression;
import org.eclipse.internal.xtend.expression.ast.Identifier;
import org.eclipse.internal.xtend.expression.ast.OperationCall;
import org.eclipse.internal.xtend.expression.ast.SyntaxElement;
import org.eclipse.internal.xtend.expression.parser.SyntaxConstants;
import org.eclipse.internal.xtend.xtend.ast.AbstractExtension;
import org.eclipse.internal.xtend.xtend.ast.AbstractExtensionDefinition;
import org.eclipse.internal.xtend.xtend.ast.Around;
import org.eclipse.internal.xtend.xtend.ast.Check;
import org.eclipse.internal.xtend.xtend.ast.Extension;
import org.eclipse.internal.xtend.xtend.ast.ExtensionFile;
import org.eclipse.jface.text.IRegion;
import org.eclipse.xpand2.XpandExecutionContext;
import org.eclipse.xtend.expression.ExecutionContext;
import org.eclipse.xtend.shared.ui.Activator;
import org.eclipse.xtend.shared.ui.core.IXtendXpandProject;
import org.eclipse.xtend.shared.ui.core.IXtendXpandResource;
import org.eclipse.xtend.shared.ui.internal.XtendLog;
/**
* Search engine for Xtend.
*
* @author Sven Efftinge (http://www.efftinge.de)
* @author Peter Friese
* @author Darius Jockel
*/
public class XtendXpandSearchEngine {
public class ExpressionVisitor extends AbstractExpressionVisitor {
public Set<OperationCall> targetStatement = new HashSet<OperationCall>();
private final IRegion region;
public ExpressionVisitor(final IRegion region, Set<OperationCall> targetStatement){
this.region = region;
this.targetStatement = targetStatement;
}
@Override
protected Object visitOperationCall(OperationCall oc) {
if (oc.getStart()<= region.getOffset() && oc.getEnd() >= region.getOffset()+region.getLength())
targetStatement.add(oc);
if (oc.getTarget() != null) {
oc.getTarget().accept(this);
}
if (oc.getParamsAsList() != null) {
for (Expression expr : oc.getParamsAsList()) {
expr.accept(this);
}
}
return oc;
}
}
public static List<Definition> findAllDefines(IXtendXpandProject project) {
List<Definition> matches = new ArrayList<Definition>();
IXtendXpandResource[] resources = project.getRegisteredResources();
for (IXtendXpandResource res : resources) {
if (res.getExtXptResource() instanceof Template) {
Template tpl = (Template) res.getExtXptResource();
for (XpandDefinition xdef : tpl.getDefinitions()) {
if (xdef instanceof Definition) {
Definition def = (Definition) xdef;
matches.add(def);
}
}
}
}
return matches;
}
public static List<SearchMatch> findAllOccurrences(IXtendXpandProject project, String identifier) {
List<SearchMatch> matches = new ArrayList<SearchMatch>();
IXtendXpandResource[] resources = project.getRegisteredResources();
for (IXtendXpandResource res : resources) {
if (res.getExtXptResource() instanceof Template) {
Template tpl = (Template) res.getExtXptResource();
for (XpandDefinition xdef : tpl.getDefinitions()) {
if (xdef instanceof Definition) {
Definition def = (Definition) xdef;
if (def.getName().equals(identifier)) {
int startOfDefnitionName = def.getDefName().getStart() - 1;
int lengthOfDefinitionName = def.getDefName().getEnd() - def.getDefName().getStart();
matches.add(new SearchMatch(startOfDefnitionName, lengthOfDefinitionName, res
.getUnderlyingStorage()));
}
}
}
}
// there is no visitor api or something similar so far, so we have
// to use a reflective mechanism...
Set<ExpandStatement> ops = findRec(res.getExtXptResource(), ExpandStatement.class, new HashSet<Object>());
for (ExpandStatement expr : ops) {
Identifier definition = expr.getDefinition();
String definitionFQN = definition.getValue();
int lastIndexOf = definitionFQN.lastIndexOf(identifier);
if (lastIndexOf > -1) {
int startOfDefinition = definition.getStart() + lastIndexOf - 1;
int lengthOfDefinition = identifier.length();
matches.add(new SearchMatch(startOfDefinition, lengthOfDefinition, res.getUnderlyingStorage()));
}
}
}
for (IProject p : project.getProject().getProject().getReferencingProjects()) {
IXtendXpandProject extxptp = Activator.getExtXptModelManager().findProject(p);
if (extxptp != null) {
matches.addAll(findAllOccurrences(extxptp, identifier));
}
}
return sort(matches);
}
/**
* Find all operation invocations for the given identifier. Ignores
* parameters and types as well as resource references (i.e. imports)
*/
public static List<SearchMatch> findReferences(IXtendXpandProject project, String identifier) {
List<SearchMatch> matches = new ArrayList<SearchMatch>();
IXtendXpandResource[] resources = project.getRegisteredResources();
for (IXtendXpandResource res : resources) {
// there is no visitor api or something similar so far, so we have
// to use a reflective mechanism...
Set<OperationCall> ops = findRec(res.getExtXptResource(), OperationCall.class, new HashSet<Object>());
for (OperationCall expr : ops) {
if (expr.getName().getValue().equals(identifier)) {
matches.add(new SearchMatch(expr.getName().getStart() + 1, expr.getName().getEnd() - expr.getStart()
, res.getUnderlyingStorage()));
}
}
}
for (IProject p : project.getProject().getProject().getReferencingProjects()) {
IXtendXpandProject extxptp = Activator.getExtXptModelManager().findProject(p);
if (extxptp != null) {
matches.addAll(findReferences(extxptp, identifier));
}
}
return sort(matches);
}
/**
* Find all extension declarations for the given identifier. Ignores
* parameters and types as well as resource references (i.e. imports)
*/
public static List<SearchMatch> findDeclarations(IXtendXpandProject project, String identifier) {
List<SearchMatch> matches = new ArrayList<SearchMatch>();
IXtendXpandResource[] resources = project.getRegisteredResources();
for (IXtendXpandResource res : resources) {
if (res.getExtXptResource() instanceof ExtensionFile) {
ExtensionFile ef = (ExtensionFile) res.getExtXptResource();
for (Extension ext : ef.getExtensions()) {
if (ext instanceof AbstractExtension) {
AbstractExtension ae = (AbstractExtension) ext;
if (ext.getName().equals(identifier)) {
Identifier id = ae.getNameIdentifier();
matches.add(new SearchMatch(id.getStart()+1, id.getEnd() - id.getStart() + 1 /*
* sorry
* for
* the
* "+1"-hack
*/, res.getUnderlyingStorage()));
}
}
}
}
if (res.getExtXptResource() instanceof Template) {
Template tpl = (Template) res.getExtXptResource();
for (XpandDefinition xdef : tpl.getDefinitions()) {
if (xdef instanceof Definition) {
Definition def = (Definition) xdef;
if (def.getName().equals(identifier)) {
matches.add(new SearchMatch(def.getDefName().getStart(), def.getDefName().getEnd()
- def.getDefName().getStart(), res.getUnderlyingStorage()));
}
}
}
}
}
try {
for (IProject p : project.getProject().getProject().getReferencedProjects()) {
IXtendXpandProject extxptp = Activator.getExtXptModelManager().findProject(p);
if (extxptp != null) {
matches.addAll(findDeclarations(extxptp, identifier));
}
}
} catch (CoreException e) {
XtendLog.logError(e);
}
return sort(matches);
}
/**
* Find all reachable Xpand declarations by name in given template <em>resource</em> or imported templates.
* Type or parameters are ignored.
*/
public static List<AbstractDefinition> findDefinitionsByNameInResourceAndImports(IXtendXpandProject project, String identifier, XpandResource resource) {
List<AbstractDefinition> matches = new ArrayList<AbstractDefinition>();
//The name of the definition
String definitionName = XpandUtil.getLastSegment(identifier);
//If the definition qualifier is qualified, the name of the template
String templateName = XpandUtil.withoutLastSegment(identifier);
XpandExecutionContext xctx = (XpandExecutionContext)Activator.getExecutionContext(project.getProject()).cloneWithResource(resource);
List<XpandResource> tpls = new ArrayList<XpandResource>();
if (templateName != null && !templateName.equals(""))
{
XpandResource xpandResource = xctx.findTemplate(templateName);
if (xpandResource != null)
tpls.add(xpandResource);
}
if (tpls.isEmpty()) {
List<IXtendXpandResource> xtendXpandResources = new ArrayList<IXtendXpandResource>();
for(String name : resource.getImportedNamespaces())
xtendXpandResources.add(Activator.getExtXptModelManager().findXtendXpandResource(
name.replaceAll(XpandUtil.NS_DELIM, "/"),
XpandUtil.TEMPLATE_EXTENSION));
for (IXtendXpandResource res : xtendXpandResources) {
if (res != null && res.getExtXptResource() instanceof XpandResource)
tpls.add((XpandResource) res.getExtXptResource());
}
tpls.add(resource);
}
for (XpandResource tpl : tpls) {
for (XpandDefinition xdef : tpl.getDefinitions()) {
if (xdef instanceof Definition) {
Definition def = (Definition) xdef;
if (XpandUtil.getLastSegment(def.getName()).equals(definitionName)) {
matches.add(def);
}
}
}
}
if (!matches.isEmpty()) {
matches.add(matches.size(), matches.get(0));
matches.remove(0);
}
return matches;
}
/**
* Find all reachable Extensions declarations with name <em>identifier</em> in given resource.
*
* @return A list of extensions imported by the IXtendXpandResource <em>resource</em> with given name <em>identifier</em>
*/
public static List<Extension> findExtensionsByNameInResourceAndImports(IXtendXpandProject project,
String identifier, IXtendXpandResource resource) {
List<Extension> matches = new ArrayList<Extension>();
ExecutionContext ctx = Activator.getExecutionContext(project.getProject());
ctx = ctx.cloneWithResource(resource.getExtXptResource());
final Set<? extends Extension> extensions = ctx.getAllExtensions();
for (Extension ext : extensions) {
if (ext.getName().equals(identifier)) {
matches.add(ext);
}
}
if (!matches.isEmpty()) {
matches.add(matches.size(), matches.get(0));
matches.remove(0);
}
return matches;
}
private static List<SearchMatch> sort(List<SearchMatch> searchmatches) {
Collections.sort(searchmatches, new Comparator<SearchMatch>() {
public int compare(SearchMatch o1, SearchMatch o2) {
if (o1.getFile() == null) {
return -1;
}
if (o2.getFile() == null) {
return 1;
}
int fileCompare = o1.getFile().getName().compareTo(o2.getFile().getName());
if (fileCompare == 0) {
return ((Integer) o1.getOffSet()).compareTo(o2.getOffSet());
}
return fileCompare;
}
});
return searchmatches;
}
@SuppressWarnings("unchecked")
private static <T extends SyntaxElement> Set<T> findRec(Object res, Class<T> clazz, Set<Object> visitedNodes) {
if (visitedNodes.contains(res)) {
return Collections.emptySet();
}
visitedNodes.add(res);
Set<T> result = new HashSet<T>();
if (clazz.isInstance(res)) {
result.add((T) res);
}
Class<?> instanceClass = res.getClass();
Method[] methods = instanceClass.getMethods();
for (Method method : methods) {
int mod = method.getModifiers();
if (Modifier.isPublic(mod) && !Modifier.isStatic(mod) && method.getName().startsWith("get")
&& method.getParameterTypes().length == 0) {
Class<?> pType = method.getReturnType();
// if it's a SyntaxElement navigate it
if (Collection.class.isAssignableFrom(pType) || SyntaxElement.class.isAssignableFrom(pType)) {
Object invRes;
try {
invRes = method.invoke(res, new Object[] {});
if (invRes instanceof Collection<?>) {
for (Object o : (Collection<?>) invRes) {
result.addAll(findRec(o, clazz, visitedNodes));
}
} else if (invRes != null) {
result.addAll(findRec(invRes, clazz, visitedNodes));
}
} catch (Exception e) {
XtendLog.logError(e);
}
}
}
}
return result;
}
/**
* Search a template for a {@link org.eclipse.internal.xtend.expression.ast.Expression Expression} inside region.
*
* @param hyperlinkRegion
* The region inside the template to search for
* @param template
* the template to search inside
*
* @return Iff found, the Expression,
* else <code>null</code>
*/
public static OperationCall findExpressionInTemplate(final IRegion region,
final Template template) {
final Set<OperationCall> targetStatement = new HashSet<OperationCall>();
AbstractDefinition[] defs = template.getAllDefinitions();
final XtendXpandSearchEngine xxse= new XtendXpandSearchEngine();
for (AbstractDefinition def : defs) {
if (!targetStatement.isEmpty())
break;
def.accept(new AbstractXpandVisitor() {
@Override
protected Object visitExpressionStatement(ExpressionStatement node){
if (node.getExpression() != null)
node.getExpression().accept(xxse.new ExpressionVisitor(region, targetStatement));
return node;
}
@Override
protected Object visitExpandStatement(ExpandStatement node){
if (node.getTarget() != null)
node.getTarget().accept(xxse.new ExpressionVisitor(region, targetStatement));
if (node.getSeparator() != null)
node.getSeparator().accept(xxse.new ExpressionVisitor(region, targetStatement));
if (node.getParameters() != null)
for (Expression param: node.getParameters())
param.accept(xxse.new ExpressionVisitor(region, targetStatement));
return node;
}
@Override
protected Object visitErrorStatement(ErrorStatement node){
if (node.getMessage() != null)
node.getMessage().accept(xxse.new ExpressionVisitor(region, targetStatement));
return node;
}
@Override
protected Object visitFileStatement(FileStatement node){
if (node.getTargetFileName() != null)
node.getTargetFileName().accept(xxse.new ExpressionVisitor(region, targetStatement));
visitChildren(node.getBodyAsList());
return node;
}
@Override
protected Object visitForEachStatement(ForEachStatement node){
if (node.getTarget() != null)
node.getTarget().accept(xxse.new ExpressionVisitor(region, targetStatement));
if (node.getSeparator() != null)
node.getSeparator().accept(xxse.new ExpressionVisitor(region, targetStatement));
visitChildren(node.getBodyAsList());
return node;
}
@Override
protected Object visitIfStatement(IfStatement node){
if (node.getCondition() != null)
node.getCondition().accept(xxse.new ExpressionVisitor(region, targetStatement));
visitChildren(node.getBodyAsList());
visitChild(node.getElseIf());
return node;
}
@Override
protected Object visitLetStatement(LetStatement node){
if (node.getVarValue() != null)
node.getVarValue().accept(xxse.new ExpressionVisitor(region, targetStatement));
visitChildren(node.getBodyAsList());
return node;
}
@Override
protected Object visitProtectStatement(ProtectStatement node){
if (node.getCommentStart() != null)
node.getCommentStart().accept(xxse.new ExpressionVisitor(region, targetStatement));
if (node.getCommentEnd() != null)
node.getCommentEnd().accept(xxse.new ExpressionVisitor(region, targetStatement));
if (node.getId() != null)
node.getId().accept(xxse.new ExpressionVisitor(region, targetStatement));
visitChildren(node.getBodyAsList());
return node;
}
});
if (!targetStatement.isEmpty())
return targetStatement.iterator().next();
}
return null;
}
/**
* Search a ExtensionFile for a {@link org.eclipse.internal.xtend.expression.ast.OperationCall OperationCall} inside region.
*
* @param hyperlinkRegion
* The region inside the template to search for
* @param extensionFile
* the ExtensionFile to search inside
*
* @return Iff found, the OperationCall,
* else <code>null</code>
*/
public static OperationCall findExpressionInExtensionFile(final IRegion region,
final ExtensionFile extensionFile) {
final Set<OperationCall> targetStatement = new HashSet<OperationCall>();
final XtendXpandSearchEngine xxse= new XtendXpandSearchEngine();
//handle Extensions
for (Extension extension : extensionFile.getExtensions()) {
if (!targetStatement.isEmpty())
break;
if (extension instanceof AbstractExtensionDefinition && extension.getStart() <= region.getOffset() &&
extension.getEnd() >= region.getOffset()+region.getLength()) {
AbstractExtensionDefinition abstractExtensionDefinition = (AbstractExtensionDefinition)extension;
abstractExtensionDefinition.getExpression().accept(xxse.new ExpressionVisitor(region, targetStatement));
}
}
//handle Arounds
for (Around around : extensionFile.getArounds()) {
if (!targetStatement.isEmpty())
break;
if (around.getStart() <= region.getOffset() &&
around.getEnd() >= region.getOffset()+region.getLength())
around.getExpression().accept(xxse.new ExpressionVisitor(region, targetStatement));
}
//handle Checks
for (Check check : extensionFile.getChecks()) {
if (!targetStatement.isEmpty())
break;
if (check.getStart() <= region.getOffset() &&
check.getEnd() >= region.getOffset()+region.getLength())
{
check.getMsg().accept(xxse.new ExpressionVisitor(region, targetStatement));
check.getConstraint().accept(xxse.new ExpressionVisitor(region, targetStatement));
if (check.getGuard() != null)
check.getGuard().accept(xxse.new ExpressionVisitor(region, targetStatement));
}
}
return targetStatement.isEmpty()?null:targetStatement.iterator().next();
}
/**
* Search a template for a {@link org.eclipse.internal.xpand2.ast.ExpandStatement ExpandStatement} inside given region.
*
* @param region
* The region inside the template to search for
* @param template
* The template to search inside
*
* @return Iff found, the ExpandStatement,
* else <code>null</code>
*/
public static ExpandStatement findDefinition(final String name, final IRegion region, Template template) {
final Set<ExpandStatement> targetStatement = new HashSet<ExpandStatement>();
for (AbstractDefinition def : template.getAllDefinitions()) {
if (!targetStatement.isEmpty())
break;
def.accept(new AbstractXpandVisitor() {
@Override
protected Object visitExpandStatement(ExpandStatement node) {
if (node.getStart() <= region.getOffset() &&
node.getEnd() >= region.getOffset()+region.getLength() &&
node.getDefinition().getValue().contains(name.trim()))
targetStatement.add(node);
return super.visitExpandStatement(node);
}
});
if (!targetStatement.isEmpty())
return targetStatement.iterator().next();
}
return null;
}
}