org.eclipse.xtext.xtext.ui.editor.quickfix.XtextGrammarQuickfixProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.xtext.ui.editor.quickfix.XtextGrammarQuickfixProvider.java

Source

/*******************************************************************************
 * Copyright (c) 2010 Michael Clay 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
 *
 *******************************************************************************/
package org.eclipse.xtext.xtext.ui.editor.quickfix;

import static org.eclipse.xtext.xtext.XtextConfigurableIssueCodes.*;

import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.net.URL;
import java.util.AbstractList;
import java.util.Collection;
import java.util.Collections;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Set;

import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.codegen.ecore.genmodel.GenModel;
import org.eclipse.emf.codegen.ecore.genmodel.GenModelPackage;
import org.eclipse.emf.codegen.ecore.genmodel.GenPackage;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EDataType;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EPackage;
import org.eclipse.emf.ecore.EStructuralFeature.Setting;
import org.eclipse.emf.ecore.EcorePackage;
import org.eclipse.emf.ecore.plugin.EcorePlugin;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.ecore.xmi.XMLResource;
import org.eclipse.emf.ecore.xmi.impl.URIHandlerImpl;
import org.eclipse.jface.dialogs.MessageDialog;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.IWorkbench;
import org.eclipse.ui.actions.WorkspaceModifyOperation;
import org.eclipse.xtext.AbstractRule;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.EnumLiteralDeclaration;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.ParserRule;
import org.eclipse.xtext.RuleCall;
import org.eclipse.xtext.TerminalRule;
import org.eclipse.xtext.XtextFactory;
import org.eclipse.xtext.XtextPackage;
import org.eclipse.xtext.conversion.IValueConverterService;
import org.eclipse.xtext.formatting.ILineSeparatorInformation;
import org.eclipse.xtext.naming.QualifiedName;
import org.eclipse.xtext.nodemodel.ICompositeNode;
import org.eclipse.xtext.nodemodel.INode;
import org.eclipse.xtext.nodemodel.util.NodeModelUtils;
import org.eclipse.xtext.resource.IEObjectDescription;
import org.eclipse.xtext.resource.IResourceDescriptions;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.resource.impl.ResourceDescriptionsProvider;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.edit.IModification;
import org.eclipse.xtext.ui.editor.model.edit.IModificationContext;
import org.eclipse.xtext.ui.editor.model.edit.ISemanticModification;
import org.eclipse.xtext.ui.editor.quickfix.DefaultQuickfixProvider;
import org.eclipse.xtext.ui.editor.quickfix.Fix;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.Strings;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.Issue;
import org.eclipse.xtext.xtext.XtextLinkingDiagnosticMessageProvider;

import com.google.common.base.CaseFormat;
import com.google.common.base.Predicate;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.google.common.collect.Sets;
import com.google.inject.Inject;

/**
 * @author Michael Clay - Initial contribution and API
 * @author Sebastian Zarnekow - Quickfixes for bogus EPackage imports
 */
public class XtextGrammarQuickfixProvider extends DefaultQuickfixProvider {

    private static final String GRAMMAR_LANG_DOC = "https://www.eclipse.org/Xtext/documentation/301_grammarlanguage.html";

    private String NULL_QUICKFIX_IMAGE = null;

    //   see https://bugs.eclipse.org/bugs/show_bug.cgi?id=324566
    //   
    //   @Fix(XtextValidator.INVALID_METAMODEL_ALIAS)
    //   public void fixInvalidMetaModelAlias(final Issue issue, IssueResolutionAcceptor acceptor) {
    //      final String alias = issue.getData()[0];
    //      acceptor.accept(issue, "Remove '" + alias + "' alias", "Remove '" + alias + "' alias", NULL_QUICKFIX_IMAGE,
    //            new ISemanticModification() {
    //               public void apply(final EObject element, IModificationContext context) {
    //                  ReferencedMetamodel referencedMetamodel = (ReferencedMetamodel) element;
    //                  referencedMetamodel.setAlias(null);
    //               }
    //            });
    //      createLinkingIssueResolutions(issue, acceptor);
    //   }

    @Inject
    private IValueConverterService valueConverterService;

    @Inject
    private ResourceDescriptionsProvider resourceDescriptionsProvider;

    @Inject(optional = true)
    private IWorkbench workbench;

    @Inject
    private ILineSeparatorInformation separatorInfo;

    @Fix(XtextLinkingDiagnosticMessageProvider.UNRESOLVED_RULE)
    public void fixUnresolvedRule(final Issue issue, IssueResolutionAcceptor acceptor) {
        final String ruleName = issue.getData()[0];
        acceptor.accept(issue, "Create rule '" + ruleName + "'", "Create rule '" + ruleName + "'",
                NULL_QUICKFIX_IMAGE, new ISemanticModification() {
                    @Override
                    public void apply(final EObject element, IModificationContext context)
                            throws BadLocationException {
                        AbstractRule abstractRule = EcoreUtil2.getContainerOfType(element, ParserRule.class);
                        ICompositeNode node = NodeModelUtils.getNode(abstractRule);
                        int offset = node.getEndOffset();
                        StringBuilder builder = new StringBuilder("\n\n");
                        if (abstractRule instanceof TerminalRule)
                            builder.append("terminal ");
                        String newRule = builder.append(ruleName).append(":\n\t\n;").toString();
                        context.getXtextDocument().replace(offset, 0, newRule);
                    }
                });
        createLinkingIssueResolutions(issue, acceptor);
    }

    @Fix(INVALID_METAMODEL_NAME)
    public void fixInvalidMetaModelName(final Issue issue, IssueResolutionAcceptor acceptor) {
        final String metaModelName = issue.getData()[0];
        acceptor.accept(issue, "Fix metamodel name '" + metaModelName + "'",
                "Fix metamodel name '" + metaModelName + "'", NULL_QUICKFIX_IMAGE, new IModification() {
                    @Override
                    public void apply(IModificationContext context) throws Exception {
                        context.getXtextDocument().replace(issue.getOffset(), issue.getLength(),
                                Strings.toFirstLower(metaModelName));
                    }
                });
    }

    @Fix(EMPTY_ENUM_LITERAL)
    public void fixEmptyEnumLiteral(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Fix empty enum literal", "Fix empty enum literal", NULL_QUICKFIX_IMAGE,
                new ISemanticModification() {
                    @Override
                    public void apply(final EObject element, IModificationContext context) {
                        EnumLiteralDeclaration enumLiteralDeclaration = (EnumLiteralDeclaration) element;
                        Keyword keyword = XtextFactory.eINSTANCE.createKeyword();
                        keyword.setValue(enumLiteralDeclaration.getEnumLiteral().getName().toLowerCase());
                        enumLiteralDeclaration.setLiteral(keyword);
                    }
                });
    }

    @Fix(INVALID_ACTION_USAGE)
    public void fixInvalidActionUsage(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Fix invalid action usage", "Fix invalid action usage", NULL_QUICKFIX_IMAGE,
                new IModification() {
                    @Override
                    public void apply(IModificationContext context) throws BadLocationException {
                        context.getXtextDocument().replace(issue.getOffset(), issue.getLength(), "");
                    }
                });
    }

    @Fix(INVALID_PACKAGE_REFERENCE_INHERITED)
    public void fixImportedPackageFromSuperGrammar(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData().length == 1)
            acceptor.accept(issue, "Change to '" + issue.getData()[0] + "'",
                    "Fix the bogus package import\n" + "import '" + issue.getData()[0] + "'", NULL_QUICKFIX_IMAGE,
                    new IModification() {
                        @Override
                        public void apply(IModificationContext context) throws BadLocationException {
                            String replaceString = valueConverterService.toString(issue.getData()[0], "STRING");
                            IXtextDocument document = context.getXtextDocument();
                            String delimiter = document.get(issue.getOffset(), 1);
                            if (!replaceString.startsWith(delimiter)) {
                                replaceString = delimiter + replaceString.substring(1, replaceString.length() - 1)
                                        + delimiter;
                            }
                            document.replace(issue.getOffset(), issue.getLength(), replaceString);
                        }
                    });
    }

    @Fix(INVALID_PACKAGE_REFERENCE_EXTERNAL)
    public void fixExternalImportedPackage(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData().length == 1)
            acceptor.accept(issue, "Update the imported package '" + issue.getData()[0] + "'",
                    "Fix the bogus package import\n" + "import '" + issue.getData()[0] + "'", NULL_QUICKFIX_IMAGE,
                    new IModification() {
                        @Override
                        public void apply(IModificationContext context) throws BadLocationException {
                            String replaceString = valueConverterService.toString(issue.getData()[0], "STRING");
                            IXtextDocument document = context.getXtextDocument();
                            final List<String> importedPackages = document
                                    .priorityReadOnly(new IUnitOfWork<List<String>, XtextResource>() {

                                        @Override
                                        public List<String> exec(XtextResource state) throws Exception {
                                            IResourceDescriptions descriptions = resourceDescriptionsProvider
                                                    .getResourceDescriptions(state);
                                            ResourceSet resourceSet = state.getResourceSet();
                                            final Map<URI, URI> uriMap = Maps.newHashMap();
                                            EPackage ePackage = loadPackageFromIndex(descriptions, resourceSet,
                                                    uriMap, issue.getData()[0]);
                                            if (ePackage != null) {
                                                final Map<String, EPackage> packagePerNsURI = Maps.newHashMap();
                                                packagePerNsURI.put(ePackage.getNsURI(), ePackage);
                                                final Set<URI> updatedReferences = fixReferencesInPackages(ePackage,
                                                        packagePerNsURI, uriMap, descriptions, resourceSet);
                                                if (updatedReferences.isEmpty())
                                                    return null;
                                                Iterator<EPackage> iterator = packagePerNsURI.values().iterator();
                                                while (iterator.hasNext()) {
                                                    EPackage pack = iterator.next();
                                                    Resource resource = pack.eResource();
                                                    if (!resource.getURI().isPlatformResource()) {
                                                        iterator.remove();
                                                    }
                                                }
                                                final List<String> result = Lists.newArrayList();
                                                new WorkspaceModifyOperation( /* workspace lock */ ) {

                                                    @Override
                                                    protected void execute(IProgressMonitor monitor)
                                                            throws CoreException, InvocationTargetException,
                                                            InterruptedException {
                                                        try {
                                                            for (EPackage pack : packagePerNsURI.values()) {
                                                                Resource resource = pack.eResource();
                                                                resource.save(Collections.singletonMap(
                                                                        XMLResource.OPTION_URI_HANDLER,
                                                                        new URIHandlerImpl.PlatformSchemeAware() {
                                                                            @Override
                                                                            public URI deresolve(URI uri) {
                                                                                // replace archive uris with platform:/plugin
                                                                                if (!uri.isArchive()
                                                                                        || !updatedReferences
                                                                                                .contains(uri)) {
                                                                                    return super.deresolve(uri);
                                                                                }
                                                                                URI withoutFragment = uri
                                                                                        .trimFragment();
                                                                                if (uriMap.containsKey(
                                                                                        withoutFragment)) {
                                                                                    withoutFragment = uriMap
                                                                                            .get(withoutFragment);
                                                                                }
                                                                                return super.deresolve(
                                                                                        withoutFragment
                                                                                                .appendFragment(uri
                                                                                                        .fragment()));
                                                                            }
                                                                        }));
                                                                result.add(resource.getURI().toString());
                                                            }
                                                        } catch (IOException ioe) {
                                                            throw new InvocationTargetException(ioe);
                                                        }
                                                    }
                                                }.run(new NullProgressMonitor());

                                                for (int i = resourceSet.getResources().size() - 1; i >= 0; i--) {
                                                    Resource resource = resourceSet.getResources().get(i);
                                                    if (!resource.getContents().isEmpty()
                                                            && resource.getContents().get(0) instanceof GenModel) {
                                                        resourceSet.getResources().remove(i);
                                                    }
                                                }
                                                return result;
                                            }
                                            return null;
                                        }

                                        private Set<URI> fixReferencesInPackages(EPackage ePackage,
                                                Map<String, EPackage> packagePerNsURI, Map<URI, URI> uriMap,
                                                IResourceDescriptions descriptions, ResourceSet resourceSet) {
                                            Set<URI> result = Sets.newHashSet();
                                            Map<EObject, Collection<Setting>> allReferences = EcoreUtil.CrossReferencer
                                                    .find(Collections.singletonList(ePackage));
                                            for (final Setting setting : Iterables.concat(allReferences.values())) {
                                                if (setting.getEStructuralFeature().isChangeable()) {
                                                    final Object referenced = setting.get(true);
                                                    List<Object> references = null;
                                                    if (referenced instanceof EObject) {
                                                        references = new AbstractList<Object>() {
                                                            @Override
                                                            public Object set(int index, Object element) {
                                                                setting.set(element);
                                                                return referenced;
                                                            }

                                                            @Override
                                                            public Object get(int index) {
                                                                return referenced;
                                                            }

                                                            @Override
                                                            public int size() {
                                                                return 1;
                                                            }
                                                        };
                                                    } else {
                                                        @SuppressWarnings("unchecked")
                                                        List<Object> casted = (List<Object>) referenced;
                                                        references = casted;
                                                    }
                                                    for (int i = 0; i < references.size(); i++) {
                                                        if (references.get(i) instanceof EObject) {
                                                            EObject referencedEObject = (EObject) references.get(i);
                                                            EPackage transitive = EcoreUtil2.getContainerOfType(
                                                                    referencedEObject, EPackage.class);
                                                            if (isRegisteredPackage(transitive)) {
                                                                if (referencedEObject instanceof EDataType)
                                                                    continue;
                                                                if (referencedEObject == EcorePackage.Literals.EOBJECT)
                                                                    continue;
                                                                EPackage fromWorkspace = packagePerNsURI
                                                                        .get(transitive.getNsURI());
                                                                if (fromWorkspace == null && !packagePerNsURI
                                                                        .containsKey(transitive.getNsURI())) {
                                                                    fromWorkspace = loadPackageFromIndex(
                                                                            descriptions, resourceSet, uriMap,
                                                                            transitive.getNsURI());
                                                                    packagePerNsURI.put(transitive.getNsURI(),
                                                                            fromWorkspace);
                                                                }
                                                                if (fromWorkspace != null) {
                                                                    String fragment = transitive.eResource()
                                                                            .getURIFragment(referencedEObject);
                                                                    EObject replacement = fromWorkspace.eResource()
                                                                            .getEObject(fragment);
                                                                    if (replacement != null) {
                                                                        result.add(EcoreUtil.getURI(replacement));
                                                                        references.set(i, replacement);
                                                                    }
                                                                }
                                                            }
                                                        }
                                                    }
                                                }
                                            }
                                            return result;
                                        }

                                        private boolean isRegisteredPackage(EPackage ePackage) {
                                            return ePackage != null && (ePackage.eResource() == null || ePackage
                                                    .getNsURI().equals(ePackage.eResource().getURI().toString()));
                                        }

                                        private EPackage loadPackageFromIndex(IResourceDescriptions descriptions,
                                                ResourceSet resourceSet, Map<URI, URI> uriMap, String nsURI) {
                                            Iterable<IEObjectDescription> fixUs = descriptions.getExportedObjects(
                                                    EcorePackage.Literals.EPACKAGE, QualifiedName.create(nsURI),
                                                    false);
                                            for (IEObjectDescription description : fixUs) {
                                                if (description.getEObjectURI().isPlatformResource()) {
                                                    EObject result = resourceSet
                                                            .getEObject(description.getEObjectURI(), true);
                                                    if (result instanceof EPackage) {
                                                        return (EPackage) result;
                                                    }
                                                }
                                            }
                                            URI genModelURI = EcorePlugin
                                                    .getEPackageNsURIToGenModelLocationMap(false).get(nsURI);
                                            if (genModelURI != null) {
                                                Resource genmodelResource = resourceSet.getResource(genModelURI,
                                                        true);
                                                GenModel genModel = (GenModel) genmodelResource.getContents()
                                                        .get(0);
                                                for (GenPackage genPackage : genModel.getGenPackages()) {
                                                    Object object = genPackage.eGet(
                                                            GenModelPackage.Literals.GEN_PACKAGE__ECORE_PACKAGE,
                                                            false);
                                                    if (object instanceof EObject) {
                                                        EObject proxy = (EObject) object;
                                                        URI proxyURI = EcoreUtil.getURI(proxy);
                                                        URI resolvedProxyURI = proxyURI.resolve(genModelURI);
                                                        if (nsURI.equals(genPackage.getEcorePackage().getNsURI())) {
                                                            EPackage result = genPackage.getEcorePackage();
                                                            uriMap.put(result.eResource().getURI(),
                                                                    resolvedProxyURI.trimFragment());
                                                            return result;
                                                        }
                                                    }
                                                }
                                            }
                                            return null;
                                        }

                                    });
                            String delimiter = document.get(issue.getOffset(), 1);
                            if (!replaceString.startsWith(delimiter)) {
                                replaceString = delimiter + replaceString.substring(1, replaceString.length() - 1)
                                        + delimiter;
                            }
                            document.replace(issue.getOffset(), issue.getLength(), replaceString);
                            if (importedPackages != null && !importedPackages.isEmpty()) {
                                final Shell shell = workbench.getActiveWorkbenchWindow().getShell();
                                shell.getDisplay().asyncExec(new Runnable() {
                                    @Override
                                    public void run() {
                                        String title = "Please update the Ecore2XtextDslProjectContributor that generates the language.";
                                        String message = "Please make sure that the Ecore2XtextDslProjectContributor that generates the language is up-to date.\n"
                                                + "Especially important is the registration of the referenced packages.\n"
                                                + "Please refer to the reference documentation for details.";

                                        MessageDialog dialog = new MessageDialog(shell, title, null, message,
                                                MessageDialog.INFORMATION,
                                                new String[] { "Open Documentation", "Close" }, 1);
                                        if (dialog.open() == 0) {
                                            try {
                                                workbench.getBrowserSupport().getExternalBrowser()
                                                        .openURL(new URL(GRAMMAR_LANG_DOC));
                                            } catch (Exception e) {
                                                // ignore
                                            }
                                        }
                                    }
                                });

                            }
                        }
                    });
    }

    @Fix(INVALID_TERMINALRULE_NAME)
    public void fixTerminalRuleName(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData().length == 1) {
            final String upperCase = CaseFormat.UPPER_CAMEL.to(CaseFormat.UPPER_UNDERSCORE, issue.getData()[0])
                    .toString();
            acceptor.accept(issue, "Change name to " + upperCase, "Change name to " + upperCase, "upcase.png",
                    new IModification() {

                        @Override
                        public void apply(IModificationContext context) throws Exception {
                            final IXtextDocument xtextDocument = context.getXtextDocument();
                            xtextDocument.replace(issue.getOffset(), issue.getLength(), upperCase);
                            xtextDocument.modify(new IUnitOfWork.Void<XtextResource>() {
                                @Override
                                public void process(XtextResource state) throws Exception {
                                    final EObject terminalRule = state
                                            .getEObject(issue.getUriToProblem().fragment());
                                    Iterable<RuleCall> candidates = Iterables.filter(Iterables
                                            .filter(Lists.newArrayList(state.getAllContents()), RuleCall.class),
                                            new Predicate<RuleCall>() {
                                                @Override
                                                public boolean apply(RuleCall ruleCall) {
                                                    return ruleCall.getRule() == terminalRule;
                                                }
                                            });
                                    for (RuleCall ruleCall : candidates) {
                                        List<INode> nodes = NodeModelUtils.findNodesForFeature(ruleCall,
                                                XtextPackage.eINSTANCE.getRuleCall_Rule());
                                        for (INode node : nodes) {
                                            ITextRegion textRegion = node.getTextRegion();
                                            xtextDocument.replace(textRegion.getOffset(), textRegion.getLength(),
                                                    upperCase);
                                        }
                                    }
                                }
                            });
                        }
                    });
        }
    }

    @Fix(EXPLICIT_OVERRIDE_MISSING)
    public void addOverrideTag(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Add missing @Override annotation", "Inserts the missing @Override annotation.",
                null, new IModification() {

                    @Override
                    public void apply(IModificationContext context) throws Exception {
                        final URI uri = issue.getUriToProblem();
                        final IXtextDocument document = context.getXtextDocument(uri);

                        if (document == null) {
                            return;
                        }

                        final Integer offset = document.readOnly(new IUnitOfWork<Integer, XtextResource>() {

                            @Override
                            public Integer exec(XtextResource state) throws Exception {
                                final EObject eObject = state.getEObject(uri.fragment());
                                if (eObject == null) {
                                    return null;
                                } else {
                                    return NodeModelUtils.findActualNodeFor(eObject).getOffset();
                                }
                            }
                        });

                        if (offset != null) {
                            document.replace(offset.intValue(), 0, "@Override " + separatorInfo.getLineSeparator());
                        }
                    }
                });
    }
}