org.eclipse.viatra.query.patternlanguage.emf.ui.contentassist.EMFPatternLanguageProposalProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.viatra.query.patternlanguage.emf.ui.contentassist.EMFPatternLanguageProposalProvider.java

Source

/*******************************************************************************
 * Copyright (c) 2010-2012, Zoltan Ujhelyi, Istvan Rath and Daniel Varro
 * 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:
 *   Zoltan Ujhelyi - initial API and implementation
 *******************************************************************************/
package org.eclipse.viatra.query.patternlanguage.emf.ui.contentassist;

import static org.eclipse.emf.ecore.util.EcoreUtil.getRootContainer;

import java.util.Objects;
import java.util.Set;

import org.eclipse.emf.ecore.EClass;
import org.eclipse.emf.ecore.EClassifier;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EEnum;
import org.eclipse.emf.ecore.EEnumLiteral;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.Region;
import org.eclipse.jface.text.contentassist.ICompletionProposal;
import org.eclipse.viatra.query.patternlanguage.emf.annotations.PatternAnnotationProvider;
import org.eclipse.viatra.query.patternlanguage.emf.vql.Annotation;
import org.eclipse.viatra.query.patternlanguage.emf.vql.ClassType;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternLanguagePackage;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PackageImport;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PathExpressionConstraint;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternModel;
import org.eclipse.viatra.query.patternlanguage.emf.helper.PatternLanguageHelper;
import org.eclipse.viatra.query.patternlanguage.emf.services.EMFPatternLanguageGrammarAccess;
import org.eclipse.viatra.query.patternlanguage.emf.vql.Pattern;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternBody;
import org.eclipse.viatra.query.patternlanguage.emf.vql.PatternCall;
import org.eclipse.viatra.query.patternlanguage.emf.vql.Variable;
import org.eclipse.viatra.query.runtime.matchers.psystem.aggregations.IAggregatorFactory;
import org.eclipse.xtext.Assignment;
import org.eclipse.xtext.CrossReference;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.EnumRule;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.access.IJvmTypeProvider;
import org.eclipse.xtext.common.types.xtext.ui.ITypesProposalProvider;
import org.eclipse.xtext.common.types.xtext.ui.TypeMatchFilters;
import org.eclipse.xtext.naming.IQualifiedNameConverter;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.ILeafNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.EObjectDescription;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.scoping.IScope;
import org.eclipse.xtext.scoping.IScopeProvider;
import org.eclipse.xtext.ui.editor.contentassist.ConfigurableCompletionProposal;
import org.eclipse.xtext.ui.editor.contentassist.ContentAssistContext;
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor;
import org.eclipse.xtext.ui.editor.contentassist.ICompletionProposalAcceptor.Delegate;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.xbase.typesystem.IExpressionScope.Anchor;

import com.google.common.base.Function;
import com.google.common.base.Functions;
import com.google.common.base.Predicates;
import com.google.common.collect.Iterables;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

/**
 * see http://www.eclipse.org/Xtext/documentation/latest/xtext.html#contentAssist on how to customize content assistant
 */
public class EMFPatternLanguageProposalProvider extends AbstractEMFPatternLanguageProposalProvider {

    private static final Set<String> FILTERED_KEYWORDS = Sets.newHashSet("pattern");

    @Inject
    private PatternAnnotationProvider annotationProvider;
    @Inject
    IScopeProvider scopeProvider;
    @Inject
    ReferenceProposalCreator crossReferenceProposalCreator;
    @Inject
    IQualifiedNameConverter nameConverter;
    @Inject
    private EMFPatternLanguageGrammarAccess ga;

    @Inject
    private ValidFeatureDescription featureDescriptionPredicate;
    @Inject
    private IJvmTypeProvider.Factory jvmTypeProviderFactory;
    @Inject
    private ITypesProposalProvider typeProposalProvider;

    @Override
    public void createProposals(ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
        // suppress content assist in comments
        if (context.getCurrentNode().getGrammarElement() == ga.getML_COMMENTRule()
                || context.getCurrentNode().getGrammarElement() == ga.getSL_COMMENTRule()) {
            return;
        }
        super.createProposals(context, acceptor);
    }

    private ICompletionProposal configurePatternProposal(ICompletionProposal original) {
        if (original instanceof ConfigurableCompletionProposal) {
            ConfigurableCompletionProposal prop = (ConfigurableCompletionProposal) original;
            prop.setTextApplier(new PatternImporter());

        }
        return original;
    }

    @Override
    protected Function<IEObjectDescription, ICompletionProposal> getProposalFactory(String ruleName,
            ContentAssistContext contentAssistContext) {
        Function<IEObjectDescription, ICompletionProposal> factory = super.getProposalFactory(ruleName,
                contentAssistContext);
        if (contentAssistContext.getCurrentNode().getSemanticElement() instanceof PatternCall
                && ga.getQualifiedNameRule().getName().equals(ruleName)) {
            factory = Functions.compose(this::configurePatternProposal, factory);
        }

        return factory;
    }

    @SuppressWarnings("restriction")
    @Override
    public void completeKeyword(Keyword keyword, ContentAssistContext contentAssistContext,
            ICompletionProposalAcceptor acceptor) {
        // ignore keywords in FILTERED set
        if (FILTERED_KEYWORDS.contains(keyword.getValue())) {
            return;
        }
        super.completeKeyword(keyword, contentAssistContext, acceptor);
    }

    @Override
    public void complete_ValueReference(EObject model, RuleCall ruleCall, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        super.complete_ValueReference(model, ruleCall, context, acceptor);
        if (model instanceof PathExpressionConstraint) {
            PathExpressionConstraint head = (PathExpressionConstraint) model;
            // XXX The following code re-specifies scoping instead of reusing the scope provider
            PatternLanguageHelper.getPathExpressionEMFTailType(head).filter(EEnum.class::isInstance)
                    .map(EEnum.class::cast).ifPresent(type -> {
                        // In case of EEnums add Enum Literal constants
                        ContentAssistContext.Builder myContextBuilder = context.copy();
                        myContextBuilder.setMatcher(new EnumPrefixMatcher(type.getName()));

                        for (EEnumLiteral literal : type.getELiterals()) {
                            ICompletionProposal completionProposal = createCompletionProposal(
                                    "::" + literal.getName(), type.getName() + "::" + literal.getName(), null,
                                    myContextBuilder.toContext());
                            acceptor.accept(completionProposal);
                        }
                    });
            // XXX The following code re-specifies scoping instead of reusing the scope provider
            // Always refer to existing variables
            PatternBody body = (PatternBody) head.eContainer()/* PatternBody */;
            for (Variable var : body.getVariables()) {
                acceptor.accept(createCompletionProposal(var.getName(), context));
            }
            Pattern pattern = (Pattern) body.eContainer();
            for (Variable var : pattern.getParameters()) {
                acceptor.accept(createCompletionProposal(var.getName(), context));
            }
        }
    }

    /**
     * @since 2.0
     */
    @Override
    public void complete_PackageImport(EObject model, RuleCall ruleCall, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        PatternModel pModel = null;
        EObject root = getRootContainer(model);
        if (root instanceof PatternModel) {
            pModel = (PatternModel) root;
        }
        ContentAssistContext.Builder myContextBuilder = context.copy();
        myContextBuilder.setMatcher(new ClassifierPrefixMatcher(context.getMatcher(), getQualifiedNameConverter()));
        ClassType type = null;
        if (model instanceof Variable) {
            type = (ClassType) ((Variable) model).getType();
        } else {
            return;
        }

        ICompositeNode node = NodeModelUtils.getNode(type);
        int offset = node.getOffset();
        Region replaceRegion = new Region(offset,
                context.getReplaceRegion().getLength() + context.getReplaceRegion().getOffset() - offset);
        myContextBuilder.setReplaceRegion(replaceRegion);
        myContextBuilder.setLastCompleteNode(node);
        StringBuilder availablePrefix = new StringBuilder(4);
        for (ILeafNode leaf : node.getLeafNodes()) {
            if (leaf.getGrammarElement() != null && !leaf.isHidden()) {
                if ((leaf.getTotalLength() + leaf.getTotalOffset()) < context.getOffset())
                    availablePrefix.append(leaf.getText());
                else
                    availablePrefix
                            .append(leaf.getText().substring(0, context.getOffset() - leaf.getTotalOffset()));
            }
            if (leaf.getTotalOffset() >= context.getOffset())
                break;
        }
        myContextBuilder.setPrefix(availablePrefix.toString());

        ContentAssistContext myContext = myContextBuilder.toContext();
        for (PackageImport declaration : PatternLanguageHelper.getPackageImportsIterable(pModel)) {
            if (declaration.getEPackage() != null) {
                createClassifierProposals(declaration, model, myContext, acceptor);
            }
        }
    }

    private void createClassifierProposals(PackageImport declaration, EObject model, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        // String alias = declaration.getAlias();
        // QualifiedName prefix = (!Strings.isEmpty(alias))
        // ? QualifiedName.create(getValueConverter().toString(alias,"ID"))
        // : null;
        boolean createDatatypeProposals = modelOrContainerIs(model, Variable.class);
        boolean createEnumProposals = modelOrContainerIs(model, EnumRule.class);
        boolean createClassProposals = modelOrContainerIs(model, Variable.class);
        Function<IEObjectDescription, ICompletionProposal> factory = getProposalFactory(null, context);
        for (EClassifier classifier : declaration.getEPackage().getEClassifiers()) {
            if (classifier instanceof EDataType && createDatatypeProposals
                    || classifier instanceof EEnum && createEnumProposals
                    || classifier instanceof EClass && createClassProposals) {
                String classifierName = getValueConverter().toString(classifier.getName(), "ID");
                QualifiedName proposalQualifiedName = /* (prefix != null) ? prefix.append(classifierName) : */QualifiedName
                        .create(classifierName);
                IEObjectDescription description = EObjectDescription.create(proposalQualifiedName, classifier);
                ConfigurableCompletionProposal proposal = (ConfigurableCompletionProposal) factory
                        .apply(description);
                if (proposal != null) {
                    /*
                     * if (prefix != null) proposal.setDisplayString(classifier.getName() + " - " + alias);
                     */
                    proposal.setPriority(proposal.getPriority() * 2);
                }
                acceptor.accept(proposal);
            }
        }
    }

    private boolean modelOrContainerIs(EObject model, Class<?>... types) {
        for (Class<?> type : types) {
            if (type.isInstance(model) || type.isInstance(model.eContainer()))
                return true;
        }
        return false;
    }

    /**
     * @since 2.0
     */
    public void complete_RefType(PathExpressionConstraint model, RuleCall ruleCall, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        EObject reference = NodeModelUtils.findActualSemanticObjectFor(context.getCurrentNode());
        // If the semantic object is a ReferenceType, calculate its index
        int index = model.getEdgeTypes().indexOf(reference);
        final int edgeCount = model.getEdgeTypes().size();
        // If the semantic object for the current node is the constraint, consider the last edge as context for the scope calculation
        if (Objects.equals(model, reference) && edgeCount > 1) {
            index = edgeCount;
        }
        // For the scope calculation, step one step back, to either the previous edge type or the source type
        IScope scope = scopeProvider.getScope(
                index > 0 ? model.getEdgeTypes().get(index - 1) : model.getSourceType(),
                PatternLanguagePackage.Literals.REFERENCE_TYPE__REFNAME);
        crossReferenceProposalCreator.lookupCrossReference(scope, model,
                PatternLanguagePackage.Literals.REFERENCE_TYPE__REFNAME, acceptor,
                Predicates.<IEObjectDescription>alwaysTrue(),
                getProposalFactory(ruleCall.getRule().getName(), context));
    }

    @Override
    public void completePackageImport_EPackage(EObject model, Assignment assignment, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        super.completePackageImport_EPackage(model, assignment, context,
                new StringProposalDelegate(acceptor, context));
    }

    /**
     * Remove extra double quote from proposal if the next character is already a double quote.
     * Based on the work of Christian Dietrich in {@link "https://christiandietrich.wordpress.com/2015/03/19/xtext-and-strings-as-cross-references/"} 
     * 
     * @author Abel Hegedus
     *
     */
    static class StringProposalDelegate extends Delegate {

        ContentAssistContext ctx;

        StringProposalDelegate(ICompletionProposalAcceptor delegate, ContentAssistContext ctx) {
            super(delegate);
            this.ctx = ctx;
        }

        @Override
        public void accept(ICompletionProposal proposal) {
            if (proposal instanceof ConfigurableCompletionProposal) {
                ConfigurableCompletionProposal prop = (ConfigurableCompletionProposal) proposal;
                int replacementLength = prop.getReplacementLength();
                int endPos = prop.getReplacementOffset() + replacementLength;
                IXtextDocument document = ctx.getDocument();
                if (document != null && document.getLength() > endPos) {
                    // We are not at the end of the file
                    try {
                        if ("\"".equals(document.get(endPos, 1))) {
                            /*
                             * XXX Updating replacementLength should be enough, however it does not work, as it will be
                             * rewritten when apply() is called on the proposal
                             */
                            String replacementString = prop.getReplacementString();
                            prop.setReplacementString(
                                    replacementString.substring(0, replacementString.length() - 1));
                        }
                    } catch (BadLocationException e) {
                        // never happens, as we already checked that the given range is valid 
                        throw new IllegalStateException("No content at position inside the document", e);
                    }
                }
            }
            super.accept(proposal);
        }

    }

    private boolean isReferrable(Variable v) {
        return v != null && !v.eIsProxy() && !v.getName().startsWith("_")
                && !PatternLanguageHelper.hasAggregateReference(v);
    }

    private boolean isReferrable(IEObjectDescription desc) {
        if (desc != null
                && EcoreUtil2.isAssignableFrom(PatternLanguagePackage.Literals.VARIABLE, desc.getEClass())) {
            Variable v = (Variable) desc.getEObjectOrProxy();
            return v != null && !v.eIsProxy() && !v.getName().startsWith("_")
                    && !PatternLanguageHelper.hasAggregateReference(v);
        }
        return false;
    }

    /**
     * @since 2.0
     */
    @Override
    public void complete_Annotation(EObject model, RuleCall ruleCall, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        for (String annotationName : annotationProvider.getAllAnnotationNames()) {
            if (annotationProvider.isDeprecated(annotationName)) {
                continue;
            }
            String prefixedName = String.format("@%s", annotationName);
            String prefix = context.getPrefix();
            ContentAssistContext modifiedContext = context;
            INode lastNode = context.getLastCompleteNode();
            if ("".equals(prefix) && lastNode.getSemanticElement() instanceof Annotation) {
                Annotation previousNode = (Annotation) lastNode.getSemanticElement();
                String annotationPrefix = previousNode.getName();
                if (previousNode.getParameters().isEmpty()
                        && !annotationProvider.getAllAnnotationNames().contains(annotationPrefix)) {
                    modifiedContext = context.copy()
                            .setReplaceRegion(
                                    new Region(lastNode.getOffset(), lastNode.getLength() + prefix.length()))
                            .toContext();
                    prefixedName = annotationName;
                }
            }
            ICompletionProposal proposal = createCompletionProposal(prefixedName, prefixedName, null,
                    modifiedContext);
            if (proposal instanceof ConfigurableCompletionProposal) {
                ((ConfigurableCompletionProposal) proposal)
                        .setAdditionalProposalInfo(annotationProvider.getAnnotationObject(annotationName));
                ((ConfigurableCompletionProposal) proposal).setHover(getHover());
            }
            acceptor.accept(proposal);
        }
    }

    /**
     * @since 2.0
     */
    @Override
    public void complete_AnnotationParameter(EObject model, RuleCall ruleCall, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        if (model instanceof Annotation) {
            Annotation annotation = (Annotation) model;
            for (String paramName : annotationProvider.getAnnotationParameters(annotation.getName())) {
                String outputName = String.format("%s = ", paramName);
                ICompletionProposal proposal = createCompletionProposal(outputName, paramName, null, context);
                if (proposal instanceof ConfigurableCompletionProposal) {
                    ((ConfigurableCompletionProposal) proposal).setAdditionalProposalInfo(
                            annotationProvider.getAnnotationParameter(annotation.getName(), paramName));
                    ((ConfigurableCompletionProposal) proposal).setHover(getHover());
                }
                acceptor.accept(proposal);
            }
        }
    }

    /**
     * @since 2.0
     */
    @Override
    public void complete_VariableReference(EObject model, RuleCall ruleCall, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        IScope scope = scopeProvider.getScope(model, PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VARIABLE);
        crossReferenceProposalCreator.lookupCrossReference(scope, model,
                PatternLanguagePackage.Literals.VARIABLE_REFERENCE__VARIABLE, acceptor, this::isReferrable,
                getProposalFactory(ruleCall.getRule().getName(), context));

    }

    /**
     * @since 2.0
     */
    @Override
    protected void createLocalVariableAndImplicitProposals(EObject context, Anchor anchor,
            ContentAssistContext contentAssistContext, ICompletionProposalAcceptor acceptor) {
        String prefix = contentAssistContext.getPrefix();
        if (prefix.length() > 0 && !Character.isJavaIdentifierStart(prefix.charAt(0))) {
            return;
        }

        final PatternBody body = EcoreUtil2.getContainerOfType(context, PatternBody.class);
        for (Variable v : Iterables.filter(body.getVariables(), this::isReferrable)) {
            ICompletionProposal proposal = createCompletionProposal(v.getName(), contentAssistContext);
            acceptor.accept(proposal);
        }

        proposeDeclaringTypeForStaticInvocation(context, null /* ignore */, contentAssistContext, acceptor);
    }

    /**
     * @since 2.0
     */
    @Override
    public void completePatternCall_PatternRef(EObject model, Assignment assignment, ContentAssistContext context,
            ICompletionProposalAcceptor acceptor) {
        /*
         * filter not local, private patterns (private patterns in other resources) this information stored in the
         * userdata of the EObjectDescription EObjectDescription only created for not local eObjects, so check for
         * resource equality is unnecessary.
         */
        lookupCrossReference(((CrossReference) assignment.getTerminal()), context, acceptor, Predicates
                .and(featureDescriptionPredicate, input -> !("true".equals(input.getUserData("private")))));
    }

    /**
     * @since 2.0
     */
    @Override
    public void completeAggregatedValue_Aggregator(EObject model, Assignment assignment,
            ContentAssistContext context, ICompletionProposalAcceptor acceptor) {
        final IJvmTypeProvider jvmTypeProvider = jvmTypeProviderFactory
                .createTypeProvider(model.eResource().getResourceSet());
        final JvmType interfaceToImplement = jvmTypeProvider.findTypeByName(IAggregatorFactory.class.getName());
        typeProposalProvider.createSubTypeProposals(interfaceToImplement, this, context,
                PatternLanguagePackage.Literals.AGGREGATED_VALUE__AGGREGATOR, TypeMatchFilters.canInstantiate(),
                acceptor);
    }

}