package org.absmodels.abs.plugin.editor;
import java.util.*;
import java.util.List;
import org.absmodels.abs.plugin.Activator;
import org.absmodels.abs.plugin.util.InternalASTNode;
import org.absmodels.abs.plugin.util.UtilityFunctions;
import org.absmodels.abs.plugin.util.UtilityFunctions.EditorPosition;
import org.eclipse.core.resources.IProject;
import org.eclipse.core.runtime.Path;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewer;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.hyperlink.AbstractHyperlinkDetector;
import org.eclipse.jface.text.hyperlink.IHyperlink;
import abs.frontend.ast.*;
import abs.frontend.typechecker.*;
import abs.frontend.typechecker.KindedName.Kind;
/**
* a hyperlink detector
* detects links in abs documents
* (ctrl + left mouse can be used to jump to declaration)
*/
public class AbsHyperlinkDetector extends AbstractHyperlinkDetector {
private abstract static class AbsHyperlink implements IHyperlink {
private final int endOffset;
private final int startOffset;
protected ABSEditor editor;
private AbsHyperlink(ABSEditor editor, int startOffset, int endOffset) {
this.editor = editor;
this.endOffset = endOffset;
this.startOffset = startOffset;
}
@Override
public String getTypeLabel() {
return null;
}
@Override
public IRegion getHyperlinkRegion() {
return new Region(startOffset, endOffset+1-startOffset);
}
}
private static final class JumpToDeclaration extends AbsHyperlink {
private final EditorPosition targetPos;
private final String name;
private JumpToDeclaration(ABSEditor editor, int startOffset, int endOffset, EditorPosition targetPos) {
this(editor, startOffset, endOffset, targetPos, "");
}
public JumpToDeclaration(ABSEditor editor, int startOffset, int endOffset, EditorPosition targetPos,
String name) {
super(editor, startOffset, endOffset);
this.targetPos = targetPos;
this.name = name;
}
@Override
public void open() {
jumpToPosition(editor, targetPos);
}
@Override
public String getHyperlinkText() {
return "Open declaration " + name;
}
}
private static final class JumpToImplementation extends AbsHyperlink {
private final String methodName;
private final Type calleeType;
public JumpToImplementation(ABSEditor editor, int startOffset, int endOffset, String methodName, Type calleeType) {
super(editor, startOffset, endOffset);
this.methodName = methodName;
this.calleeType = calleeType;
}
@Override
public void open() {
InterfaceDecl i = (InterfaceDecl) calleeType.getDecl();
final List<MethodImpl> implementingMethods = new ArrayList<MethodImpl>();
for (HasTypeHierarchy t : i.getSubTypes()) {
if (t instanceof ClassDecl) {
ClassDecl c = (ClassDecl) t;
MethodImpl m = c.lookupMethod(methodName);
if (m != null) {
implementingMethods.add(m);
}
}
}
if (implementingMethods.size() == 1) {
// only one implementing method => directly jump to it
jumpToPosition(editor, getPosition(implementingMethods.get(0)));
} else if (implementingMethods.size() > 1) {
// more than one implementing method => show alternatives in a list
AbsInformationPresenter p = editor.getInformationPresenter();
p.setInformationControl(new HyperlinkInformationControl(
editor, "Select implementing class ...", implementingMethods));
p.showInformation();
}
}
@Override
public String getHyperlinkText() {
return "Open implementation";
}
}
private ABSEditor editor;
public AbsHyperlinkDetector(ABSEditor editor) {
this.editor = editor;
}
@Override
public IHyperlink[] detectHyperlinks(ITextViewer textViewer, final IRegion region, boolean canShowMultipleHyperlinks) {
return getHyperlinks(editor, region.getOffset());
}
public static IHyperlink[] getHyperlinks(ABSEditor editor, int offset) {
try {
InternalASTNode<CompilationUnit> cu = editor.getCompilationUnit();
if (cu == null) {
return null;
}
IDocument doc = editor.getDocumentProvider().getDocument(editor.getEditorInput());
EditorPosition targetPos = null;
ASTNode<?> node;
ASTNode<?> decl;
synchronized (cu.getNature()) {
node = UtilityFunctions.getASTNodeOfOffset(doc, cu.getASTNode(), offset);
if (node == null) {
return null;
}
decl = getDecl(cu.getASTNode(), node);
targetPos = getPosition(decl);
}
if (targetPos == null) {
return null;
}
final int startOffset = getOffset(doc, node.getStartLine(), node.getStartColumn());
final int endOffset = getOffset(doc, node.getEndLine(), node.getEndColumn());
if (decl instanceof MethodSig) {
MethodSig methodSig = (MethodSig) decl;
if (methodSig.getContextDecl() instanceof InterfaceDecl) {
// decl is an interface method
Type typ = new InterfaceType((InterfaceDecl) methodSig.getContextDecl());
if (node instanceof Call) {
// in case of a call the type can be determined more exactly
Call call = (Call) node;
typ = call.getCallee().getType();
}
return new IHyperlink[]{
new JumpToDeclaration(editor, startOffset, endOffset, targetPos),
new JumpToImplementation(editor, startOffset, endOffset, methodSig.getName(), typ)
};
} else if (methodSig.getContextDecl() instanceof ClassDecl) {
// cursor is on a class method => provide links to methods in interfaces
ClassDecl classDecl = (ClassDecl) methodSig.getContextDecl();
List<IHyperlink> links = new ArrayList<IHyperlink>();
Set<MethodSig> addedMethods = new HashSet<MethodSig>();
for (InterfaceDecl sup : classDecl.getSuperTypes()) {
MethodSig lookupMethod = sup.lookupMethod(methodSig.getName());
if (lookupMethod != null && !addedMethods.contains(lookupMethod)) {
addedMethods.add(lookupMethod);
targetPos = getPosition(lookupMethod);
String name = lookupMethod.getContextDecl().getName() + "." + lookupMethod.getName();
links.add(new JumpToDeclaration(editor, startOffset, endOffset, targetPos, name));
}
}
if (links.size() > 0) {
return links.toArray(new IHyperlink[links.size()]);
} else {
return null;
}
}
}
return new IHyperlink[]{
new JumpToDeclaration(editor, startOffset, endOffset, targetPos)
};
} catch (BadLocationException e) {
e.printStackTrace();
return null;
}
}
/**
* gets the offset for a given line & column
* @param doc
* @param position line & column
* @return
* @throws BadLocationException
*/
private static int getOffset(IDocument doc, int line, int col) throws BadLocationException {
return doc.getLineOffset(line-1) + col-1;
}
/**
* jumps to a given position
*
* @param currentEditor the current editor
* @param pos position to jump to. Must be in same project as current editor.
*/
public static void jumpToPosition(ABSEditor currentEditor, EditorPosition pos) {
IProject project = currentEditor.getProject();
boolean opened = UtilityFunctions.jumpToPosition(project, pos);
if (!opened) {
currentEditor.openInformation("File not found!",
"Could not find file "+pos.getPath().toOSString());
return;
}
}
/**
* returns the position of a given node
*/
public static EditorPosition getPosition(ASTNode<?> node) {
if(node == null || node instanceof UnknownDecl){
return null;
}
CompilationUnit declcu = node.getCompilationUnit();
return new EditorPosition(new Path(declcu.getFileName()), node.getStartLine(), node.getStartColumn(), node.getEndLine(), node.getEndColumn());
}
/**
* get the declaration associated with a given node
*/
private static ASTNode<?> getDecl(CompilationUnit cu, ASTNode<?> node) {
ASTNode<?> decl = null;
try {
if(node instanceof FnApp){
FnApp fnapp = (FnApp)node;
String name = fnapp.getName();
decl = fnapp.lookup(new KindedName(Kind.FUN, name));
} else if(node instanceof VarOrFieldUse){
VarOrFieldUse vofu = (VarOrFieldUse)node;
String name = vofu.getName();
decl = vofu.lookupVarOrFieldName(name,false);
} else if (node instanceof PatternVarUse) {
PatternVarUse patternVarUse = (PatternVarUse) node;
String name = patternVarUse.getName();
decl = patternVarUse.lookupVarOrFieldName(name, false);
} else if(node instanceof Call){
Call call = (Call)node;
String mname = call.getMethod();
if (call.getCallee() instanceof ThisExp) {
ClassDecl classDecl = (ClassDecl) call.getContextDecl();
decl = classDecl.lookupMethod(mname);
} else {
Type type = call.getCallee().getType();
decl = type.lookupMethod(mname);
}
} else if(node instanceof DataConstructorExp){
DataConstructorExp exp = (DataConstructorExp)node;
decl = exp.getDecl();
} else if (node instanceof ConstructorPattern) {
ConstructorPattern exp = (ConstructorPattern) node;
if (exp.getType().isDataType()) {
DataTypeType type = (DataTypeType) exp.getType();
decl = type.getDecl();
}
} else if(node instanceof TypeUse){
TypeUse tu = (TypeUse)node;
decl = tu.getDecl();
} else if(node instanceof NewExp){
NewExp newexp = (NewExp)node;
String classname = newexp.getClassName();
decl = newexp.lookup(new KindedName(Kind.CLASS, classname));
} else if(node instanceof FromImport){
FromImport fimport = (FromImport)node;
String moduleName = fimport.getModuleName();
decl = fimport.lookupModule(moduleName);
} else if(node instanceof StarImport){
StarImport fimport = (StarImport)node;
String moduleName = fimport.getModuleName();
decl = fimport.lookupModule(moduleName);
} else if(node instanceof FromExport){
FromExport fexport = (FromExport)node;
decl = fexport.getModuleDecl();
} else if(node instanceof StarExport){
StarExport fexport = (StarExport)node;
decl = fexport.getModuleDecl();
} else if(node instanceof Name){
Name name = (Name)node;
String simpleName = name.getString();
decl = cu.lookupModule(simpleName);
} else if (node instanceof DeltaClause) {
DeltaClause d = (DeltaClause) node;
decl = getDeltaDecl(cu,d.getDeltaspec());
} else if (node instanceof Deltaspec) {
Deltaspec deltaspec = (Deltaspec) node;
decl = getDeltaDecl(cu,deltaspec);
} else if (node instanceof DeltaID) {
decl = getDeltaDecl(cu, ((DeltaID)node).getName());
} else if (node instanceof Feature) {
return null; // TODO #395
} else if (node instanceof ModifyClassModifier) {
ModifyClassModifier cm = (ModifyClassModifier) node;
decl = cm.findClass();
} else if (node instanceof ModifyInterfaceModifier) {
ModifyInterfaceModifier m = (ModifyInterfaceModifier) node;
decl = m.findInterface();
// TODO: handle all other Modify/Remove-modifiers
} else if (node instanceof MethodSig) {
decl = node;
}
} catch (TypeCheckerException e) {
// Nada - may come from resolveName() on broken models.
Activator.logException(e);
}
return decl;
}
private static DeltaDecl getDeltaDecl(CompilationUnit cu, String dName) {
Model m = cu.getModel();
assert m != null : dName;
return m.getDeltaDeclsMap().get(dName);
/* There may be more than one! Think of delta D(True) when ... delta(False) when.
* We pick the first one.
[ramus] I don't see how there could be more than one DeltaDecl with the same name.
*/
}
private static DeltaDecl getDeltaDecl(CompilationUnit cu, Deltaspec deltaspec) {
return getDeltaDecl(cu,deltaspec.getDeltaID());
}
}