org.eclipse.xtext.ui.refactoring.impl.DefaultReferenceUpdater.java Source code

Java tutorial

Introduction

Here is the source code for org.eclipse.xtext.ui.refactoring.impl.DefaultReferenceUpdater.java

Source

/*******************************************************************************
 * Copyright (c) 2010 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.xtext.ui.refactoring.impl;

import static org.eclipse.ltk.core.refactoring.RefactoringStatus.*;

import java.util.Iterator;

import org.apache.log4j.Logger;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.SubMonitor;
import org.eclipse.emf.common.util.URI;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.ltk.core.refactoring.RefactoringStatus;
import org.eclipse.text.edits.ReplaceEdit;
import org.eclipse.text.edits.TextEdit;
import org.eclipse.xtext.CrossReference;
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.ILocationInFileProvider;
import org.eclipse.xtext.resource.IReferenceDescription;
import org.eclipse.xtext.resource.XtextResource;
import org.eclipse.xtext.serializer.sequencer.ITransientValueService;
import org.eclipse.xtext.ui.refactoring.ElementRenameArguments;
import org.eclipse.xtext.ui.refactoring.IRefactoringUpdateAcceptor;
import org.eclipse.xtext.ui.refactoring.impl.RefactoringCrossReferenceSerializer.RefTextEvaluator;
import org.eclipse.xtext.util.ITextRegion;

import com.google.common.collect.Multimap;
import com.google.inject.Inject;

/**
 * Creates updates for all references from Xtext based resources to a renamed element using Xtext's serialization API.
 * 
 * @author Jan Koehnlein - Initial contribution and API
 */
public class DefaultReferenceUpdater extends AbstractReferenceUpdater {

    private static final Logger log = Logger.getLogger(DefaultReferenceUpdater.class);

    @Inject
    private ILocationInFileProvider locationInFileProvider;

    @Inject
    private ITransientValueService transientValueService;

    /**
     * @since 2.4 Replaces the obsolete CrossReferenceSerializerFacade
     */
    @Inject
    private RefactoringCrossReferenceSerializer crossReferenceSerializer;

    @Override
    protected void createReferenceUpdates(ElementRenameArguments elementRenameArguments,
            Multimap<URI, IReferenceDescription> resource2references, ResourceSet resourceSet,
            IRefactoringUpdateAcceptor updateAcceptor, IProgressMonitor monitor) {
        SubMonitor progress = SubMonitor.convert(monitor, "Creating reference updates",
                resource2references.keySet().size());
        for (URI referringResourceURI : resource2references.keySet()) {
            if (progress.isCanceled())
                return;
            Resource referringResource = resourceSet.getResource(referringResourceURI, false);
            if (!(referringResource instanceof XtextResource)) {
                updateAcceptor.getRefactoringStatus().add(ERROR, "Resource {0} is not an XtextResource.",
                        referringResource.getURI(), resourceSet);
            } else {
                Iterable<IReferenceDescription> referenceDescriptions = resource2references
                        .get(referringResourceURI);
                processReferringResource(referringResource, referenceDescriptions, elementRenameArguments,
                        updateAcceptor);
            }
            progress.worked(1);
        }
    }

    /**
     * Override this method for pre- or post-processing hooks.
     */
    protected void processReferringResource(Resource referringResource,
            Iterable<IReferenceDescription> referenceDescriptions, ElementRenameArguments elementRenameArguments,
            IRefactoringUpdateAcceptor updateAcceptor) {
        ((XtextResource) referringResource).getCache().clear(referringResource);
        for (IReferenceDescription referenceDescription : referenceDescriptions) {
            createReferenceUpdate(referenceDescription, referringResource.getURI(), elementRenameArguments,
                    referringResource.getResourceSet(), updateAcceptor);
        }
    }

    protected void createReferenceUpdate(IReferenceDescription referenceDescription, URI referringResourceURI,
            ElementRenameArguments elementRenameArguments, ResourceSet resourceSet,
            IRefactoringUpdateAcceptor updateAcceptor) {
        URI referringElementNewURI = elementRenameArguments
                .getNewElementURI(referenceDescription.getSourceEObjectUri());
        EObject referringElement = resourceSet.getEObject(referringElementNewURI, false);
        URI targetElementNewURI = elementRenameArguments
                .getNewElementURI(referenceDescription.getTargetEObjectUri());
        EObject newTargetElement = resourceSet.getEObject(targetElementNewURI, false);
        if (newTargetElement == null) {
            updateAcceptor.getRefactoringStatus().add(RefactoringStatus.ERROR, "Cannot find new target element.",
                    targetElementNewURI);
            log.error("Cannot find new target element. ReferringElement:" + referringElement
                    + " targetElementNewURI:" + targetElementNewURI);
            return;
        }
        createReferenceUpdate(referringElement, referringResourceURI, referenceDescription.getEReference(),
                referenceDescription.getIndexInList(), newTargetElement, updateAcceptor);
    }

    protected void createReferenceUpdate(EObject referringElement, URI referringResourceURI, EReference reference,
            int indexInList, EObject newTargetElement, IRefactoringUpdateAcceptor updateAcceptor) {
        if (!transientValueService.isValueInListTransient(referringElement, indexInList, reference)) {
            ITextRegion referenceTextRegion = locationInFileProvider.getFullTextRegion(referringElement, reference,
                    indexInList);
            CrossReference crossReference = getCrossReference(referringElement, referenceTextRegion.getOffset());
            if (crossReference != null) {
                RefTextEvaluator refTextComparator = getRefTextEvaluator(referringElement, referringResourceURI,
                        reference, indexInList, newTargetElement);
                String newReferenceText = crossReferenceSerializer.getCrossRefText(referringElement, crossReference,
                        newTargetElement, refTextComparator, referenceTextRegion,
                        updateAcceptor.getRefactoringStatus());
                if (newReferenceText == null) {
                    newReferenceText = resolveNameConflict(referringElement, reference, newTargetElement,
                            updateAcceptor);
                }
                if (newReferenceText == null) {
                    updateAcceptor.getRefactoringStatus().add(RefactoringStatus.ERROR,
                            "Refactoring introduces a name conflict.", referringElement, referenceTextRegion);
                }
                createTextChange(referenceTextRegion, newReferenceText, referringElement, newTargetElement,
                        reference, referringResourceURI, updateAcceptor);
            }
        }
    }

    /**
     * Return null if it is not possible to resolve a name conflict; otherwise a name which should be used.
     * @param updateAcceptor 
     * @since 2.6
     */
    protected String resolveNameConflict(EObject referringElement, EReference reference, EObject newTargetElement,
            IRefactoringUpdateAcceptor updateAcceptor) {
        return null;
    }

    /**
     * The result is used to determine the best new link text in case of multiple possibilities.
     * By default, the shortest text is chosen.
     * 
     * @since 2.4
     */
    protected RefTextEvaluator getRefTextEvaluator(EObject referringElement, URI referringResourceURI,
            EReference reference, int indexInList, EObject newTargetElement) {
        // by default choose the shortest text
        return new RefTextEvaluator() {
            @Override
            public boolean isValid(IEObjectDescription target) {
                return true;
            }

            @Override
            public boolean isBetterThan(String newText, String currentText) {
                return newText.length() < currentText.length();
            }
        };
    }

    protected void createTextChange(ITextRegion referenceTextRegion, String newReferenceText,
            EObject referringElement, EObject newTargetElement, EReference reference, URI referringResourceURI,
            IRefactoringUpdateAcceptor updateAcceptor) {
        if (newReferenceText != null) {
            TextEdit referenceEdit = new ReplaceEdit(referenceTextRegion.getOffset(),
                    referenceTextRegion.getLength(), newReferenceText);
            updateAcceptor.accept(referringResourceURI, referenceEdit);
        }
    }

    protected CrossReference getCrossReference(EObject referringElement, int offset) {
        ICompositeNode node = NodeModelUtils.getNode(referringElement);
        if (node != null) {
            Iterator<INode> iter = node.getAsTreeIterable().iterator();
            while (iter.hasNext()) {
                INode childNode = iter.next();
                if (childNode.getOffset() >= offset && childNode.getGrammarElement() instanceof CrossReference)
                    return (CrossReference) childNode.getGrammarElement();
            }
        }
        return null;
    }

    protected ILocationInFileProvider getLocationInFileProvider() {
        return locationInFileProvider;
    }

    protected ITransientValueService getTransientValueService() {
        return transientValueService;
    }

}