org.eclipse.xtend.ide.quickfix.XtendQuickfixProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtend.ide.quickfix.XtendQuickfixProvider.java

Source

/*******************************************************************************
 * Copyright (c) 2011, 2018 itemis AG (http://www.itemis.eu) 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.xtend.ide.quickfix;

import static com.google.common.collect.Sets.*;
import static org.eclipse.xtext.ui.util.DisplayRunHelper.*;
import static org.eclipse.xtext.util.Strings.*;

import java.util.ArrayList;
import java.util.List;
import java.util.Set;

import org.apache.log4j.Logger;
import org.eclipse.core.resources.IFile;
import org.eclipse.core.resources.IResource;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IPath;
import org.eclipse.core.runtime.NullProgressMonitor;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.jdt.core.IJavaProject;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.xtend.core.jvmmodel.IXtendJvmAssociations;
import org.eclipse.xtend.core.services.XtendGrammarAccess;
import org.eclipse.xtend.core.validation.IssueCodes;
import org.eclipse.xtend.core.xtend.XtendClass;
import org.eclipse.xtend.core.xtend.XtendExecutable;
import org.eclipse.xtend.core.xtend.XtendField;
import org.eclipse.xtend.core.xtend.XtendFile;
import org.eclipse.xtend.core.xtend.XtendFunction;
import org.eclipse.xtend.core.xtend.XtendMember;
import org.eclipse.xtend.core.xtend.XtendPackage;
import org.eclipse.xtend.core.xtend.XtendTypeDeclaration;
import org.eclipse.xtend.ide.buildpath.XtendLibClasspathAdder;
import org.eclipse.xtend.ide.codebuilder.InsertionOffsets;
import org.eclipse.xtend.ide.codebuilder.MemberFromSuperImplementor;
import org.eclipse.xtext.EcoreUtil2;
import org.eclipse.xtext.Keyword;
import org.eclipse.xtext.common.types.JvmConstructor;
import org.eclipse.xtext.common.types.JvmDeclaredType;
import org.eclipse.xtext.common.types.JvmGenericType;
import org.eclipse.xtext.common.types.JvmIdentifiableElement;
import org.eclipse.xtext.common.types.JvmOperation;
import org.eclipse.xtext.common.types.JvmType;
import org.eclipse.xtext.common.types.JvmTypeReference;
import org.eclipse.xtext.common.types.TypesPackage;
import org.eclipse.xtext.common.types.access.jdt.IJavaProjectProvider;
import org.eclipse.xtext.common.types.util.TypeReferences;
import org.eclipse.xtext.diagnostics.Diagnostic;
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.XtextResource;
import org.eclipse.xtext.ui.editor.model.IXtextDocument;
import org.eclipse.xtext.ui.editor.model.edit.ICompositeModification;
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.Fix;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolution;
import org.eclipse.xtext.ui.editor.quickfix.IssueResolutionAcceptor;
import org.eclipse.xtext.ui.refactoring.impl.ProjectUtil;
import org.eclipse.xtext.util.ITextRegion;
import org.eclipse.xtext.util.StopWatch;
import org.eclipse.xtext.util.concurrent.IUnitOfWork;
import org.eclipse.xtext.validation.Issue;
import org.eclipse.xtext.xbase.XBlockExpression;
import org.eclipse.xtext.xbase.XExpression;
import org.eclipse.xtext.xbase.XFeatureCall;
import org.eclipse.xtext.xbase.annotations.xAnnotations.XAnnotation;
import org.eclipse.xtext.xbase.typesystem.IBatchTypeResolver;
import org.eclipse.xtext.xbase.typesystem.override.OverrideHelper;
import org.eclipse.xtext.xbase.typesystem.override.ResolvedConstructor;
import org.eclipse.xtext.xbase.typesystem.override.ResolvedFeatures;
import org.eclipse.xtext.xbase.typesystem.references.LightweightTypeReference;
import org.eclipse.xtext.xbase.ui.contentassist.ReplacingAppendable;
import org.eclipse.xtext.xbase.ui.document.DocumentSourceAppender.Factory.OptionalParameters;
import org.eclipse.xtext.xbase.ui.quickfix.XbaseQuickfixProvider;

import com.google.common.base.Objects;
import com.google.common.collect.Iterables;
import com.google.common.collect.Lists;
import com.google.inject.Inject;
import com.google.inject.Singleton;

/**
 * @author Jan Koehnlein - Quickfixes for inconsistent indentation
 * @author Sebastian Zarnekow - Quickfixes for misspelled types and constructors
 * @author Holger Schill - Quickfixes for missing methods / fields / localVars
 */
@Singleton
public class XtendQuickfixProvider extends XbaseQuickfixProvider {

    private static final Logger logger = Logger.getLogger(XtendQuickfixProvider.class);

    @Inject
    private IXtendJvmAssociations associations;

    @Inject
    private ReplacingAppendable.Factory appendableFactory;

    @Inject
    private XtendGrammarAccess grammarAccess;

    @Inject
    private InsertionOffsets insertionOffsets;

    @Inject
    private MemberFromSuperImplementor superMemberImplementor;

    @Inject
    private XtendLibClasspathAdder xtendLibAdder;

    @Inject
    private IJavaProjectProvider projectProvider;

    @Inject
    private CreateXtendTypeQuickfixes createTypeQuickfixes;

    @Inject
    private CreateMemberQuickfixes createMemberQuickfixes;

    @Inject
    private OverrideHelper overrideHelper;

    @Inject
    private IBatchTypeResolver batchTypeResolver;

    @Inject
    private ProjectUtil projectUtil;

    @Inject
    private TypeReferences typeReferences;

    private static final Set<String> LINKING_ISSUE_CODES = newHashSet(Diagnostic.LINKING_DIAGNOSTIC,
            IssueCodes.JAVA_DOC_LINKING_DIAGNOSTIC,
            org.eclipse.xtext.xbase.validation.IssueCodes.INVALID_ARGUMENT_TYPES,
            org.eclipse.xtext.xbase.validation.IssueCodes.INVALID_NUMBER_OF_ARGUMENTS);

    @Override
    public boolean hasResolutionFor(String issueCode) {
        return LINKING_ISSUE_CODES.contains(issueCode) || super.hasResolutionFor(issueCode);
    }

    @Override
    public List<IssueResolution> getResolutions(Issue issue) {
        StopWatch stopWatch = new StopWatch(logger);
        try {
            if (LINKING_ISSUE_CODES.contains(issue.getCode())) {
                List<IssueResolution> result = new ArrayList<IssueResolution>();
                result.addAll(getResolutionsForLinkingIssue(issue));
                return result;
            } else
                return super.getResolutions(issue);
        } finally {
            stopWatch.resetAndLog("#getResolutions");
        }
    }

    @Override
    protected EReference getUnresolvedEReference(Issue issue, EObject target) {
        EReference reference = super.getUnresolvedEReference(issue, target);
        if (reference == null) {
            if (issue.getCode().equals(IssueCodes.JAVA_DOC_LINKING_DIAGNOSTIC)) {
                return TypesPackage.Literals.JVM_PARAMETERIZED_TYPE_REFERENCE__TYPE;
            }
        }
        return reference;
    }

    @Override
    protected void createLinkingIssueQuickfixes(Issue issue, IssueResolutionAcceptor issueResolutionAcceptor,
            IXtextDocument xtextDocument, XtextResource resource, EObject referenceOwner,
            EReference unresolvedReference) throws Exception {
        javaTypeQuickfixes.addQuickfixes(issue, issueResolutionAcceptor, xtextDocument, resource, referenceOwner,
                unresolvedReference);
        createTypeQuickfixes.addQuickfixes(issue, issueResolutionAcceptor, xtextDocument, resource, referenceOwner,
                unresolvedReference);
        createMemberQuickfixes.addQuickfixes(issue, issueResolutionAcceptor, xtextDocument, resource,
                referenceOwner, unresolvedReference);
    }

    @Fix(IssueCodes.INCONSISTENT_INDENTATION)
    public void fixIndentation(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Correct indentation", "Correctly indents this line in this rich string",
                "fix_indent.gif", new IModification() {
                    @Override
                    public void apply(IModificationContext context) throws Exception {
                        context.getXtextDocument().replace(issue.getOffset(), issue.getLength(),
                                issue.getData()[0]);
                    }
                });
    }

    @Fix(IssueCodes.MISSING_OVERRIDE)
    public void fixMissingOverride(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Change 'def' to 'override'", "Marks this function as 'override'", "fix_indent.gif",
                new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        replaceKeyword(grammarAccess.getMethodModifierAccess().findKeywords("def").get(0),
                                "override", element, context.getXtextDocument());
                        if (element instanceof XtendFunction) {
                            XtendFunction function = (XtendFunction) element;
                            for (XAnnotation anno : Lists.reverse(function.getAnnotations())) {
                                if (anno != null && anno.getAnnotationType() != null && Override.class.getName()
                                        .equals(anno.getAnnotationType().getIdentifier())) {
                                    ICompositeNode node = NodeModelUtils.findActualNodeFor(anno);
                                    context.getXtextDocument().replace(node.getOffset(), node.getLength(), "");
                                }
                            }
                        }
                    }
                });
    }

    @Fix(IssueCodes.OBSOLETE_OVERRIDE)
    public void fixObsoleteOverride(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Change 'override' to 'def'", "Removes 'override' from this function",
                "fix_indent.gif", new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        replaceKeyword(grammarAccess.getMethodModifierAccess().findKeywords("override").get(0),
                                "def", element, context.getXtextDocument());
                        if (element instanceof XtendFunction) {
                            XtendFunction function = (XtendFunction) element;
                            for (XAnnotation anno : Lists.reverse(function.getAnnotations())) {
                                if (anno != null && anno.getAnnotationType() != null && Override.class.getName()
                                        .equals(anno.getAnnotationType().getIdentifier())) {
                                    ICompositeNode node = NodeModelUtils.findActualNodeFor(anno);
                                    context.getXtextDocument().replace(node.getOffset(), node.getLength(), "");
                                }
                            }
                        }
                    }
                });
    }

    @Fix(IssueCodes.OBSOLETE_ANNOTATION_OVERRIDE)
    public void fixObsoleteOverrideAnnotation(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Remove superfluous @Override",
                "Removes superfluous @Override annotation from this function", "fix_indent.gif",
                new IModification() {
                    @Override
                    public void apply(IModificationContext context) throws Exception {
                        context.getXtextDocument().replace(issue.getOffset(), issue.getLength(), "");
                    }
                });
    }

    @Fix(IssueCodes.MISSING_SYNCHRONIZED)
    public void fixMissingSynchronizedKeyword(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Mark operation as synchronized", "Marks this operation as synchronized",
                "fix_indent.gif", new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        if (element instanceof XtendFunction) {
                            ((XtendFunction) element).getModifiers().add("synchronized");
                        }
                    }
                });
    }

    protected void replaceKeyword(Keyword keyword, String replacement, EObject container, IXtextDocument document)
            throws BadLocationException {
        ICompositeNode node = NodeModelUtils.findActualNodeFor(container);
        if (node != null) {
            for (ILeafNode leafNode : node.getLeafNodes()) {
                if (leafNode.getGrammarElement() == keyword) {
                    ITextRegion leafRegion = leafNode.getTextRegion();
                    String actualReplacement = replacement;
                    if (!Character.isWhitespace(document.getChar(leafRegion.getOffset() - 1))) {
                        actualReplacement = " " + replacement;
                    }
                    document.replace(leafRegion.getOffset(), leafRegion.getLength(), actualReplacement);
                }
            }
        }
    }

    @Fix(IssueCodes.MISSING_CONSTRUCTOR)
    public void addConstuctorFromSuper(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData() != null) {
            for (int i = 0; i < issue.getData().length; i += 2) {
                final URI constructorURI = URI.createURI(issue.getData()[i]);
                String javaSignature = issue.getData()[i + 1];
                String xtendSignature = "new" + javaSignature.substring(javaSignature.indexOf('('));
                acceptor.accept(issue, "Add constructor " + xtendSignature, "Add constructor " + xtendSignature,
                        "fix_indent.gif", new ISemanticModification() {
                            @Override
                            public void apply(EObject element, IModificationContext context) throws Exception {
                                XtendClass clazz = (XtendClass) element;
                                JvmGenericType inferredType = associations.getInferredType(clazz);
                                ResolvedFeatures features = overrideHelper.getResolvedFeatures(inferredType);
                                ReplacingAppendable appendable = appendableFactory.create(
                                        context.getXtextDocument(), (XtextResource) clazz.eResource(),
                                        insertionOffsets.getNewConstructorInsertOffset(null, clazz), 0,
                                        new OptionalParameters() {
                                            {
                                                ensureEmptyLinesAround = true;
                                                baseIndentationLevel = 1;
                                            }
                                        });
                                EObject constructor = clazz.eResource().getResourceSet().getEObject(constructorURI,
                                        true);
                                if (constructor instanceof JvmConstructor) {
                                    superMemberImplementor.appendConstructorFromSuper(clazz,
                                            new ResolvedConstructor((JvmConstructor) constructor,
                                                    features.getType()),
                                            appendable);
                                }
                                appendable.commitChanges();
                            }
                        });
            }
        }
    }

    @Fix(IssueCodes.CLASS_MUST_BE_ABSTRACT)
    public void implementAbstractMethods(final Issue issue, IssueResolutionAcceptor acceptor) {
        doOverrideMethods(issue, acceptor, "Add unimplemented methods");
        acceptor.accept(issue, "Make class abstract", "Make class abstract", "fix_indent.gif",
                new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        internalDoAddAbstractKeyword(element, context);
                    }
                });
    }

    @Fix(IssueCodes.ANONYMOUS_CLASS_MISSING_MEMBERS)
    public void implementAbstractMethodsInAnonymousClass(final Issue issue, IssueResolutionAcceptor acceptor) {
        doOverrideMethods(issue, acceptor, "Add unimplemented methods");
    }

    @Fix(IssueCodes.CONFLICTING_DEFAULT_METHODS)
    public void overrideDefaultMethod(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData() != null) {
            for (String data : issue.getData()) {
                int separatorIndex = data.indexOf('|');
                if (separatorIndex > 0) {
                    String interfaceName = data.substring(0, separatorIndex);
                    String uri = data.substring(separatorIndex + 1);
                    doOverrideMethods(issue, acceptor, "Override conflicting method of type " + interfaceName,
                            new String[] { uri });
                }
            }
        }
    }

    protected void doOverrideMethods(final Issue issue, IssueResolutionAcceptor acceptor, String label) {
        if (issue.getData() != null && issue.getData().length > 0) {
            doOverrideMethods(issue, acceptor, label, issue.getData());
        }
    }

    protected void doOverrideMethods(final Issue issue, IssueResolutionAcceptor acceptor, String label,
            final String[] operationUris) {
        acceptor.accept(issue, label, label, "fix_indent.gif", new ISemanticModification() {
            @Override
            public void apply(EObject element, IModificationContext context) throws Exception {
                XtendTypeDeclaration clazz = (XtendTypeDeclaration) element;
                JvmGenericType inferredType = (JvmGenericType) associations.getInferredType(clazz);
                ResolvedFeatures resolvedOperations = overrideHelper.getResolvedFeatures(inferredType);
                IXtextDocument document = context.getXtextDocument();
                final int offset = insertionOffsets.getNewMethodInsertOffset(null, clazz);
                int currentIndentation = appendableFactory.getIndentationLevelAtOffset(offset, document,
                        (XtextResource) clazz.eResource());
                final int indentationToUse = clazz.getMembers().isEmpty() ? currentIndentation + 1
                        : currentIndentation;
                ReplacingAppendable appendable = appendableFactory.create(document,
                        (XtextResource) clazz.eResource(), offset, 0, new OptionalParameters() {
                            {
                                ensureEmptyLinesAround = true;
                                baseIndentationLevel = indentationToUse;
                            }
                        });
                boolean isFirst = true;
                for (String operationUriAsString : operationUris) {
                    URI operationURI = URI.createURI(operationUriAsString);
                    EObject overridden = clazz.eResource().getResourceSet().getEObject(operationURI, true);
                    if (overridden instanceof JvmOperation) {
                        if (!isFirst)
                            appendable.newLine().newLine();
                        isFirst = false;

                        superMemberImplementor.appendOverrideFunction(clazz,
                                resolvedOperations.getResolvedOperation((JvmOperation) overridden), appendable);
                    }
                }
                appendable.commitChanges();
            }
        });
    }

    @Fix(org.eclipse.xtext.xbase.validation.IssueCodes.UNHANDLED_EXCEPTION)
    public void addThrowsDeclaration(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData() != null && issue.getData().length > 0)
            acceptor.accept(issue, "Add throws declaration", "Add throws declaration", "fix_indent.gif",
                    new ISemanticModification() {
                        @Override
                        public void apply(EObject element, IModificationContext context) throws Exception {
                            String[] issueData = issue.getData();
                            XtendExecutable xtendExecutable = EcoreUtil2.getContainerOfType(element,
                                    XtendExecutable.class);
                            XtextResource xtextResource = (XtextResource) xtendExecutable.eResource();
                            List<JvmType> exceptions = getExceptions(issueData, xtextResource);
                            if (exceptions.size() > 0) {
                                int insertPosition;
                                if (xtendExecutable.getExpression() == null) {
                                    ICompositeNode functionNode = NodeModelUtils.findActualNodeFor(xtendExecutable);
                                    if (functionNode == null)
                                        throw new IllegalStateException("functionNode may not be null");
                                    insertPosition = functionNode.getEndOffset();
                                } else {
                                    ICompositeNode expressionNode = NodeModelUtils
                                            .findActualNodeFor(xtendExecutable.getExpression());
                                    if (expressionNode == null)
                                        throw new IllegalStateException("expressionNode may not be null");
                                    insertPosition = expressionNode.getOffset();
                                }
                                ReplacingAppendable appendable = appendableFactory.create(
                                        context.getXtextDocument(), (XtextResource) xtendExecutable.eResource(),
                                        insertPosition, 0);
                                if (xtendExecutable.getExpression() == null)
                                    appendable.append(" ");
                                EList<JvmTypeReference> thrownExceptions = xtendExecutable.getExceptions();
                                if (thrownExceptions.isEmpty())
                                    appendable.append("throws ");
                                else
                                    appendable.append(", ");
                                for (int i = 0; i < exceptions.size(); i++) {
                                    appendable.append(exceptions.get(i));
                                    if (i != exceptions.size() - 1) {
                                        appendable.append(", ");
                                    }
                                }
                                if (xtendExecutable.getExpression() != null)
                                    appendable.append(" ");
                                appendable.commitChanges();
                            }
                        }
                    });
    }

    /**
     * @param all but the last element in the issue data is considered to be a URI to an exception type.
     */
    protected List<JvmType> getExceptions(String[] issueData, XtextResource resource) {
        List<JvmType> exceptions = Lists.newArrayList();
        for (int i = 0; i < issueData.length - 1; i++) {
            URI exceptionURI = URI.createURI(issueData[i]);
            EObject exception = resource.getResourceSet().getEObject(exceptionURI, true);
            if (exception instanceof JvmType) {
                exceptions.add((JvmType) exception);
            }
        }
        return exceptions;
    }

    @Fix(org.eclipse.xtext.xbase.validation.IssueCodes.UNHANDLED_EXCEPTION)
    public void surroundWithTryCatch(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData() == null || issue.getData().length <= 1) {
            return;
        }
        IModificationContext modificationContext = getModificationContextFactory().createModificationContext(issue);
        IXtextDocument xtextDocument = modificationContext.getXtextDocument();
        if (xtextDocument == null) {
            return;
        }
        if (isJvmConstructorCall(xtextDocument, issue)) {
            return;
        }
        acceptor.accept(issue, "Surround with try/catch block", "Surround with try/catch block", "fix_indent.gif",
                new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        String[] issueData = issue.getData();
                        URI childURI = URI.createURI(issueData[issueData.length - 1]);
                        XtextResource xtextResource = (XtextResource) element.eResource();
                        List<JvmType> exceptions = getExceptions(issueData, xtextResource);
                        if (exceptions.size() > 0) {
                            EObject childThrowingException = xtextResource.getResourceSet().getEObject(childURI,
                                    true);
                            XExpression toBeSurrounded = findContainerExpressionInBlockExpression(
                                    childThrowingException);
                            IXtextDocument xtextDocument = context.getXtextDocument();
                            if (toBeSurrounded != null) {
                                ICompositeNode toBeSurroundedNode = NodeModelUtils
                                        .findActualNodeFor(toBeSurrounded);
                                if (toBeSurroundedNode == null)
                                    throw new IllegalStateException("toBeSurroundedNode may not be null");
                                ITextRegion toBeSurroundedRegion = toBeSurroundedNode.getTextRegion();
                                ReplacingAppendable appendable = appendableFactory.create(
                                        context.getXtextDocument(),
                                        (XtextResource) childThrowingException.eResource(),
                                        toBeSurroundedRegion.getOffset(), toBeSurroundedRegion.getLength());
                                appendable.append("try {").increaseIndentation().newLine().append(xtextDocument
                                        .get(toBeSurroundedRegion.getOffset(), toBeSurroundedRegion.getLength()))
                                        .decreaseIndentation().newLine();
                                for (JvmType exceptionType : exceptions) {
                                    appendable.append("} catch (").append(exceptionType).append(" exc) {")
                                            .increaseIndentation().newLine()
                                            .append("throw new RuntimeException(\"auto-generated try/catch\", exc)")
                                            .decreaseIndentation().newLine();
                                }
                                appendable.append("}");
                                appendable.commitChanges();
                            }
                        }
                    }
                });
    }

    private Boolean isJvmConstructorCall(IXtextDocument xtextDocument, final Issue issue) {
        return xtextDocument.readOnly(new IUnitOfWork<Boolean, XtextResource>() {

            @Override
            public Boolean exec(XtextResource xtextResource) throws Exception {
                String[] issueData = issue.getData();
                URI childURI = URI.createURI(issueData[issueData.length - 1]);
                EObject childThrowingException = xtextResource.getResourceSet().getEObject(childURI, true);
                if (!(childThrowingException instanceof XFeatureCall)) {
                    return false;
                }
                XFeatureCall featureCall = (XFeatureCall) childThrowingException;
                return featureCall.getFeature() instanceof JvmConstructor;
            }

        });
    }

    @Fix(IssueCodes.WRONG_PACKAGE)
    public void fixPackageName(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData() != null && issue.getData().length == 1) {
            final String expectedPackage = issue.getData()[0];
            acceptor.accept(issue, "Change package declaration to '" + expectedPackage + "'",
                    "Change package declaration to '" + expectedPackage + "'", "package_obj.gif",
                    new ISemanticModification() {
                        @Override
                        public void apply(EObject element, IModificationContext context) throws Exception {
                            XtendFile file = (XtendFile) element;
                            String newPackageName = isEmpty(expectedPackage) ? null : expectedPackage;
                            String oldPackageName = file.getPackage();
                            for (EObject obj : file.eResource().getContents()) {
                                if (obj instanceof JvmDeclaredType) {
                                    JvmDeclaredType type = (JvmDeclaredType) obj;
                                    String typePackage = type.getPackageName();
                                    if (Objects.equal(typePackage, oldPackageName) || typePackage != null
                                            && typePackage.startsWith(oldPackageName + ".")) {
                                        type.internalSetIdentifier(null);
                                        type.setPackageName(newPackageName);
                                    }
                                }
                            }
                            file.setPackage(newPackageName);
                        }
                    });
        }
    }

    @Fix(IssueCodes.WRONG_FILE)
    public void fixFileName(final Issue issue, IssueResolutionAcceptor acceptor) {
        if (issue.getData() != null && issue.getData().length == 1) {
            final String expectedFileName = issue.getData()[0];
            final IFile iFile = projectUtil.findFileStorage(issue.getUriToProblem(), true);
            final IPath pathToMoveTo = iFile.getParent().getFullPath().append(expectedFileName)
                    .addFileExtension(iFile.getFileExtension());
            if (!iFile.getWorkspace().getRoot().exists(pathToMoveTo)) {
                final String label = "Rename file to '" + expectedFileName + ".xtend'";
                acceptor.accept(issue, label, label, "xtend_file.png", new IModification() {
                    @Override
                    public void apply(IModificationContext context) throws Exception {
                        runAsyncInDisplayThread(new Runnable() {
                            @Override
                            public void run() {
                                try {
                                    iFile.move(pathToMoveTo, IResource.KEEP_HISTORY, null);
                                } catch (CoreException e) {
                                    logger.error(e);
                                }
                            }
                        });
                    }
                });
            }
        }
    }

    protected XExpression findContainerExpressionInBlockExpression(EObject expr) {
        if (expr == null)
            return null;
        EObject container = expr.eContainer();
        if (container instanceof XBlockExpression)
            return (XExpression) expr;
        else
            return findContainerExpressionInBlockExpression(container);
    }

    @Fix(IssueCodes.XBASE_LIB_NOT_ON_CLASSPATH)
    public void putXtendOnClasspath(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Add Xtend libs to classpath", "Add Xtend libs to classpath", "fix_indent.gif",
                new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        ResourceSet resourceSet = element.eResource().getResourceSet();
                        IJavaProject javaProject = projectProvider.getJavaProject(resourceSet);
                        xtendLibAdder.addLibsToClasspath(javaProject, new NullProgressMonitor());
                    }
                });
    }

    @Fix(IssueCodes.MISSING_ABSTRACT)
    public void makeClassAbstract(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Make class abstract", "Make class abstract", "fix_indent.gif",
                new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        internalDoAddAbstractKeyword(element, context);
                    }
                });
    }

    @Fix(IssueCodes.API_TYPE_INFERENCE)
    public void specifyTypeExplicitly(Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Infer type", "Infer type", null, new ISemanticModification() {
            @Override
            public void apply(EObject element, IModificationContext context) throws Exception {
                EStructuralFeature featureAfterType = null;
                JvmIdentifiableElement jvmElement = null;
                if (element instanceof XtendFunction) {
                    XtendFunction function = (XtendFunction) element;
                    if (function.getCreateExtensionInfo() == null) {
                        featureAfterType = XtendPackage.Literals.XTEND_FUNCTION__NAME;
                    } else {
                        featureAfterType = XtendPackage.Literals.XTEND_FUNCTION__CREATE_EXTENSION_INFO;
                    }
                    jvmElement = associations.getDirectlyInferredOperation((XtendFunction) element);
                } else if (element instanceof XtendField) {
                    featureAfterType = XtendPackage.Literals.XTEND_FIELD__NAME;
                    jvmElement = associations.getJvmField((XtendField) element);
                }

                if (jvmElement != null) {
                    LightweightTypeReference type = batchTypeResolver.resolveTypes(element)
                            .getActualType(jvmElement);
                    INode node = Iterables.getFirst(NodeModelUtils.findNodesForFeature(element, featureAfterType),
                            null);

                    if (node == null) {
                        throw new IllegalStateException("Could not determine node for " + element);
                    }
                    if (type == null) {
                        throw new IllegalStateException("Could not determine type for " + element);
                    }
                    ReplacingAppendable appendable = appendableFactory.create(context.getXtextDocument(),
                            (XtextResource) element.eResource(), node.getOffset(), 0);
                    appendable.append(type);
                    appendable.append(" ");
                    appendable.commitChanges();
                }
            }
        });
    }

    @Fix(IssueCodes.IMPLICIT_RETURN)
    public void fixImplicitReturn(final Issue issue, IssueResolutionAcceptor acceptor) {
        acceptor.accept(issue, "Add \"return\" keyword", "Add \"return\" keyword", null,
                new ISemanticModification() {
                    @Override
                    public void apply(EObject element, IModificationContext context) throws Exception {
                        ICompositeNode node = NodeModelUtils.findActualNodeFor(element);
                        ReplacingAppendable appendable = appendableFactory.create(context.getXtextDocument(),
                                (XtextResource) element.eResource(), node.getOffset(), 0);
                        appendable.append("return ");
                        appendable.commitChanges();
                    }
                });
    }

    @Fix(IssueCodes.UNNECESSARY_MODIFIER)
    public void removeUnnecessaryModifier(final Issue issue, IssueResolutionAcceptor acceptor) {
        String[] issueData = issue.getData();
        if (issueData == null || issueData.length == 0) {
            return;
        }

        String modifier = issueData[0];

        // use the same label, description and image
        // to be able to use the quickfixes (issue resolution) in batch mode
        String label = "Remove the unnecessary modifier.";
        String description = "The modifier is unnecessary and could be removed.";
        String image = "fix_indent.gif";
        acceptor.acceptMulti(issue, label, description, image,
                (ICompositeModification<XtendMember>) (element, ctx) -> {
                    ctx.setUpdateCrossReferences(false);
                    ctx.setUpdateRelatedFiles(false);
                    ctx.addModification(element, ele -> ele.getModifiers().remove(modifier));
                });
    }

    @Fix(IssueCodes.INVALID_RETURN_TYPE_IN_CASE_OF_JUNIT_ANNOTATION)
    public void changeJUnitMethodReturnTypeToVoid(final Issue issue, IssueResolutionAcceptor acceptor) {
        // use the same label, description and image
        // to be able to use the quickfixes (issue resolution) in batch mode
        String label = "Change return type to void.";
        String description = "Change the return type to void to be recognized by JUnit.";
        String image = "fix_indent.gif";
        acceptor.acceptMulti(issue, label, description, image,
                (ICompositeModification<XtendFunction>) (element, ctx) -> {
                    ctx.setUpdateCrossReferences(false);
                    ctx.setUpdateRelatedFiles(false);
                    ctx.addModification(element,
                            ele -> ele.setReturnType(typeReferences.getTypeForName(Void.TYPE, ele)));
                });
    }

    protected void internalDoAddAbstractKeyword(EObject element, IModificationContext context)
            throws BadLocationException {
        if (element instanceof XtendFunction) {
            element = element.eContainer();
        }
        if (element instanceof XtendClass) {
            XtendClass clazz = (XtendClass) element;
            IXtextDocument document = context.getXtextDocument();
            ICompositeNode clazzNode = NodeModelUtils.findActualNodeFor(clazz);
            if (clazzNode == null)
                throw new IllegalStateException("Cannot determine node for clazz " + clazz.getName());
            int offset = -1;
            for (ILeafNode leafNode : clazzNode.getLeafNodes()) {
                if (leafNode.getText().equals("class")) {
                    offset = leafNode.getOffset();
                }
            }
            ReplacingAppendable appendable = appendableFactory.create(document, (XtextResource) clazz.eResource(),
                    offset, 0);
            appendable.append("abstract ");
            appendable.commitChanges();
        }
    }

}