/**
* Copyright (c) 2009-2011, The HATS Consortium. All rights reserved.
* This file is licensed under the terms of the Modified BSD License.
*/
package org.absmodels.abs.plugin.editor.contentassist;
import static org.absmodels.abs.plugin.util.Images.NO_IMAGE;
import static org.absmodels.abs.plugin.util.Images.getImageForASTNode;
import static org.absmodels.abs.plugin.util.UtilityFunctions.*;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import org.absmodels.abs.plugin.editor.ABSEditor;
import org.absmodels.abs.plugin.editor.contentassist.ABSCompletionProcessor.Qualifier;
import org.absmodels.abs.plugin.editor.outline.ABSContentOutlineUtils;
import org.absmodels.abs.plugin.util.InternalASTNode;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.contentassist.CompletionProposal;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import abs.frontend.ast.*;
import abs.frontend.typechecker.KindedName;
import abs.frontend.typechecker.KindedName.Kind;
import abs.frontend.typechecker.ResolvedName;
import abs.frontend.typechecker.Type;
import abs.frontend.typechecker.TypeCheckerException;
/**
* Class generating the actual proposals for auto completion
* @author mweber, tfischer
*/
public class ProposalFactory{
private static final class ProposalComparator implements
Comparator<ICompletionProposal> {
@Override
public int compare(ICompletionProposal o1, ICompletionProposal o2) {
return o1.getDisplayString().compareTo(o2.getDisplayString());
}
}
private String qualifier;
private int documentOffset;
private final IDocument doc;
private List<ICompletionProposal> proposals;
private final ABSEditor editor;
/**
* Initializes the {@link ProposalFactory} by parsing all abs files in the current project and
* also parsing the document of the currently open abs file with all unsaved changes.
* @param qualifier the qualifier typed so far
* @param documentOffset the document offset the cursor is located at
* @param doc the currently open document (possibly with non-saved changes
* @param editor the currently open editor
* @param proposals the list of proposals to be filled
*/
public ProposalFactory(Qualifier qualifier, IDocument doc, ABSEditor editor,
List<ICompletionProposal> proposals){
this.qualifier = qualifier.getQualifier();
this.documentOffset = qualifier.getOffset();
this.doc = doc;
this.proposals = proposals;
this.editor = editor;
// parse editor content (without typechecking):
editor.setCaretPos(documentOffset);
editor.reconcile(false);
}
/**
* Creates the list of completion proposals for the current qualifier. The list contains all top level elements
* as well as methods and fields for incomplete access (dot or exclamation mark).
* The list of keywords has to be provided by the frontend.
* @see abs.frontend.parser.Keywords
*/
public void computeStructureProposals() {
InternalASTNode<CompilationUnit> internalCu = editor.getCompilationUnit();
if(internalCu==null) {
addKeywordProposals();
return;
}
CompilationUnit cu = internalCu.getASTNode();
try {
ASTNode<?> node = getASTNodeOfOffset(doc, cu, documentOffset);
int tmpOffset = documentOffset;
tmpOffset--;
while(doc.getChar(tmpOffset)==' '){
tmpOffset--;
}
ASTNode<?> accessNode = getASTNodeOfOffset(doc, cu, tmpOffset);
addMainblockProposals(node);
if(accessNode instanceof IncompleteAccess){
addIncompleteAccessProposals(accessNode);
} else if(accessNode instanceof IncompleteNewExp){
addClassProposals(node);
} else if (accessNode instanceof FieldUse) {
addFieldUseProposals((FieldUse)accessNode);
} else {
addKeywordProposals();
addToplevelProposals(node);
addClassFieldProposals(node);
}
} catch (BadLocationException e) {
standardExceptionHandling(e);
}
}
/**
* adds the abs keywords to the list of proposals
*/
private void addKeywordProposals() {
//Loop through keywords
for(String s : abs.frontend.parser.Keywords.getKeywords()){
if(qualifierIsPrefixOf(s)){
CompletionProposal proposal = new CompletionProposal(s, documentOffset, qualifier.length(),
s.length(), NO_IMAGE, s, null, "");
proposals.add(proposal);
}
}
Collections.sort(proposals, new ProposalComparator());
}
private void addFieldUseProposals(FieldUse accessNode) {
if(accessNode == null){
throw new IllegalArgumentException("AccessNode may not be null!");
}
Type type = accessNode.getContextDecl().getType();
addMethodProposal(type);
}
/**
* add the variables of the main block
* @param node the node under the cursor
*/
private void addMainblockProposals(ASTNode<?> node) {
ProposalComparator comp = new ProposalComparator();
ArrayList<ICompletionProposal> temp = new ArrayList<ICompletionProposal>();
MainBlock mainblock = (MainBlock)node.calcContextNode(MainBlock.class);
if(mainblock!=null){
for(VarDecl vardecl : mainblock.getVars()){
String name = vardecl.getName();
if(qualifierIsPrefixOf(name)){
CompletionProposal proposal = new CompletionProposal(name, documentOffset, qualifier.length(),
name.length(), getImageForASTNode(vardecl), name, null, getAdditionalProposalInfo(vardecl));
temp.add(proposal);
}
}
Collections.sort(temp, comp);
proposals.addAll(0, temp);
}
}
/**
* add the classes in the current module
* @param node the node under the cursor
*/
private void addClassProposals(ASTNode<?> node) {
ProposalComparator comp = new ProposalComparator();
ArrayList<ICompletionProposal> tempNonqual = new ArrayList<ICompletionProposal>();
ArrayList<ICompletionProposal> tempQual = new ArrayList<ICompletionProposal>();
ModuleDecl moddecl = node.getModuleDecl();
// Only crash when debugging:
assert moddecl != null : "Node is not in a Module!";
if (moddecl == null) return;
Map<KindedName, ResolvedName> visibleNames = moddecl.getVisibleNames();
for(Entry<KindedName, ResolvedName> kentry : visibleNames.entrySet()){
KindedName kname = kentry.getKey();
if(qualifierIsPrefixOf(kname.getName()) && kname.getKind()==Kind.CLASS){
CompletionProposal proposal = makeVisibleNameProposal(kentry.getValue(), kname);
if(kname.isQualified()){
tempQual.add(proposal);
} else {
tempNonqual.add(proposal);
}
}
}
Collections.sort(tempNonqual, comp);
proposals.addAll(0, tempNonqual);
Collections.sort(tempQual, comp);
proposals.addAll(0, tempQual);
}
/**
* add proposals for the incomplete access under the cursor. An incomplete access exists if
* the user enters a dot or an exclamation mark after an identifier. An incomplete access
* proposal can be a method of an interface or a field
* @param accessNode the node under the cursor
*/
private void addIncompleteAccessProposals(ASTNode<?> accessNode) {
if(accessNode == null){
throw new IllegalArgumentException("AccessNode may not be null!");
}
IncompleteAccess ia = (IncompleteAccess)accessNode;
PureExp target = ia.getTarget();
Type type = target.getType();
addMethodProposal(type);
}
private void addMethodProposal(Type type) {
if (type.isFutureType()) {
proposals.clear();
String name = "get";
proposals.add( new CompletionProposal(name, documentOffset, qualifier.length(),
name.length(), null, name, null, null));
}
ArrayList<ICompletionProposal> temp = new ArrayList<ICompletionProposal>();
for(MethodSig methodSig : type.getAllMethodSigs()){
String name = methodSig.getName();
if(qualifierIsPrefixOf(name)){
CompletionProposal proposal = makeMethodSigProposal(methodSig, name);
temp.add(proposal);
}
}
Collections.sort(temp, new ProposalComparator());
proposals.addAll(0, temp);
temp.clear();
for(FieldDecl fdecl : type.getAllFieldDecls()){
String name = fdecl.getName();
if(qualifierIsPrefixOf(name)){
CompletionProposal proposal = new CompletionProposal(name, documentOffset, qualifier.length(),
name.length(), getImageForASTNode(fdecl), name, null, getAdditionalProposalInfo(fdecl));
temp.add(proposal);
}
}
Collections.sort(temp, new ProposalComparator());
proposals.addAll(0, temp);
}
private CompletionProposal makeMethodSigProposal(MethodSig methodSig, String name) {
String visibleName = ABSContentOutlineUtils.formatMethodSig(methodSig).toString();
String replacement = name+"()";
int cursorposition = name.length()+1;
Decl classorinterfacedecl = methodSig.getContextDecl();
Type type = classorinterfacedecl.getType();
visibleName += " -- " + type.getSimpleName();
CompletionProposal proposal = new CompletionProposal(replacement, documentOffset, qualifier.length(),
cursorposition, getImageForASTNode(methodSig), visibleName, null, getAdditionalProposalInfo(methodSig));
return proposal;
}
/**
* Checks, if the given String starts with the current qualifier
* @param name String to compare to qualifier
* @return true if String begins with qualifier (not case sensitive), false otherwise.
*/
public boolean qualifierIsPrefixOf(String name) {
if(name == null){
throw new IllegalArgumentException("Name may not be null!");
}
return name.toLowerCase().startsWith(qualifier.toLowerCase());
}
/**
* add the fields and variables of the class the cursor is in.
* @param node the node under the cursor
*/
private void addClassFieldProposals(ASTNode<?> node) {
ArrayList<ICompletionProposal> temp = new ArrayList<ICompletionProposal>();
MethodImpl methodimpl = node.getContextMethod();
if(methodimpl!=null){
for(VarDecl varDecl : methodimpl.getBlock().getVars()){
String name = varDecl.getName();
if(qualifierIsPrefixOf(name)){
CompletionProposal proposal = new CompletionProposal(name, documentOffset, qualifier.length(),
name.length(), getImageForASTNode(varDecl), name, null, getAdditionalProposalInfo(varDecl));
temp.add(proposal);
}
}
}
ClassDecl classdecl = (ClassDecl)node.calcContextNode(ClassDecl.class);
if(classdecl!=null){
for(FieldDecl fieldDecl : classdecl.getType().getAllFieldDecls()){
String name = fieldDecl.getName();
if(qualifierIsPrefixOf(name)){
CompletionProposal proposal = new CompletionProposal(name, documentOffset, qualifier.length(),
name.length(), getImageForASTNode(fieldDecl), name, null, getAdditionalProposalInfo(fieldDecl));
temp.add(proposal);
}
}
}
Collections.sort(temp, new ProposalComparator());
proposals.addAll(0, temp);
}
/**
* add proposals for all visible names.
* @param node the node under the cursor
*/
private void addToplevelProposals(ASTNode<?> node) {
ProposalComparator comp = new ProposalComparator();
ArrayList<ICompletionProposal> tempNonqual = new ArrayList<ICompletionProposal>();
ArrayList<ICompletionProposal> tempQual = new ArrayList<ICompletionProposal>();
ModuleDecl moddecl = node.getModuleDecl();
if(moddecl == null){
return;
}
try {
Map<KindedName, ResolvedName> visibleNames = moddecl.getVisibleNames();
for(Entry<KindedName, ResolvedName> kentry : visibleNames.entrySet()){
KindedName kname = kentry.getKey();
if(qualifierIsPrefixOf(kname.getName())){
CompletionProposal proposal = makeVisibleNameProposal(kentry.getValue(), kname);
if(kname.isQualified()){
tempQual.add(proposal);
} else {
tempNonqual.add(proposal);
}
}
}
Collections.sort(tempNonqual, comp);
proposals.addAll(tempNonqual);
Collections.sort(tempQual, comp);
proposals.addAll(tempQual);
} catch (TypeCheckerException e ) {
// ignore all type check exceptions
}
}
private CompletionProposal makeVisibleNameProposal(ResolvedName resolvedName, KindedName kname) {
Decl decl = resolvedName.getDecl();
String name = kname.getName();
String visibleName = name;
String replacement = name;
int cursorposition = name.length();
switch (kname.getKind()) {
case DATA_CONSTRUCTOR:
case FUN:
replacement = name+"()";
cursorposition = name.length()+1;
break;
case TYPE_DECL:
if (decl instanceof ParametricDataTypeDecl) {
ParametricDataTypeDecl parametricDataTypeDecl = (ParametricDataTypeDecl) decl;
if (parametricDataTypeDecl.getTypeParameterList().getNumChild() > 0) {
replacement = name+"<>";
cursorposition = name.length()+1;
}
}
break;
default:
break;
}
CompletionProposal proposal = new CompletionProposal(replacement, documentOffset, qualifier.length(),
cursorposition, getImageForASTNode(decl), visibleName, null, getAdditionalProposalInfo(decl));
return proposal;
}
private String getAdditionalProposalInfo(Decl decl){
return decl.qualifiedName();
}
private String getAdditionalProposalInfo(MethodSig methodsig){
return ABSContentOutlineUtils.formatMethodSig(methodsig).toString();
}
private String getAdditionalProposalInfo(TypedVarOrFieldDecl vofdecl){
return ABSContentOutlineUtils.formatTypedVarOrFieldDecl(vofdecl).toString();
}
}