org.grails.ide.eclipse.refactoring.rename.ParticipantChangeManager.java Source code

Java tutorial

Introduction

Here is the source code for org.grails.ide.eclipse.refactoring.rename.ParticipantChangeManager.java

Source

/*******************************************************************************
 * Copyright (c) 2012 VMWare, Inc.
 * 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
 * https://www.eclipse.org/legal/epl-v10.html
 *
 * Contributors:
 *     VMWare, Inc. - initial API and implementation
 *******************************************************************************/
package org.grails.ide.eclipse.refactoring.rename;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.core.refactoring.CompilationUnitChange;
import org.eclipse.jdt.groovy.core.util.ReflectionUtils;
import org.eclipse.ltk.core.refactoring.Change;
import org.eclipse.ltk.core.refactoring.CompositeChange;
import org.eclipse.ltk.core.refactoring.TextChange;
import org.eclipse.ltk.core.refactoring.TextFileChange;
import org.eclipse.ltk.core.refactoring.participants.RefactoringParticipant;
import org.eclipse.text.edits.MultiTextEdit;
import org.eclipse.text.edits.TextEdit;

/**
 * Manages changes made by a participant. This is a little tricky because participant must keep changes to
 * files that no one else has modified separate from those that others have already modified.
 * <p>
 * Also it is important that text changes are returned as "preChanges" to ensure that they get executed before
 * the files that they are modifying may be moved elsewhere.
 * <p>
 * This class is meant to help doing that so that the participant doesn't have to worry about this. Changes
 * added to the manager will be examined recursively. Changes to something that someone else is also changing
 * will be merged with the existing changes in the participant. Other changes will be kept separately and
 * managed in similar way. In the end, after the participant has added all the changes they must request the
 * "new" changes that weren't already merged into the host refactoring's state and return them.
 * 
 * @author Kris De Volder
 * @since 2.7
 */
public class ParticipantChangeManager {

    //   private RefactoringParticipant participant;
    private ArrayList<Change> otherChanges; //Contains all non-text-based changes made by the participant
    private Map<Object, TextChange> textChanges = new HashMap<Object, TextChange>();
    private String name;
    private RefactoringParticipant participant;

    public ParticipantChangeManager(RefactoringParticipant participant) {
        this.name = participant.getName();
        //      this.participant = participant;
        //      this.newTextChanges = new CompositeChange("Update references for Grails related Types");
        this.otherChanges = new ArrayList<Change>();
    }

    /**
     * @return The changes that were *not* merged with changes made by others. This returns only
     * those changes that aren't text changes.
     */
    public CompositeChange getOtherChanges() {
        if (!otherChanges.isEmpty()) {
            return new CompositeChange(name + " other changes",
                    otherChanges.toArray(new Change[otherChanges.size()]));
        }
        return null;
    }

    public void add(Change change) {
        if (change instanceof CompositeChange) {
            //composite... look at the children
            for (Change child : ((CompositeChange) change).getChildren()) {
                add(child);
            }
        } else if (change instanceof TextChange) {
            //non-composite text edit
            TextChange extra = (TextChange) change;

            Object el = change.getModifiedElement();
            TextChange existing = getTextChange(el);
            if (existing == null) {
                addNewTextChange(extra);
            } else {
                merge(existing, extra);
            }
        } else {
            //Something that's neither composite nor a text change... keep it as "new".
            addNewChange(change);
        }
    }

    private void addNewTextChange(TextChange change) {
        Object el = change.getModifiedElement();
        Assert.isLegal(!textChanges.containsKey(el));
        if (change.getParent() != null) {
            ReflectionUtils.setPrivateField(Change.class, "fParent", change, null); //Hack! needs to be null to lift it out of one change tree into another!
        }
        textChanges.put(el, change);
    }

    private void addNewChange(Change change) {
        if (change.getParent() != null) {
            ReflectionUtils.setPrivateField(Change.class, "fParent", change, null); //Hack! needs to be null to lift it out of one change tree into another!
        }
        otherChanges.add(change);
    }

    /**
     * @return An existing text change associated with given element or null, if no text change
     * is associated with that element so far.
     */
    private TextChange getTextChange(Object element) {
        TextChange change = textChanges.get(element);
        return change;
    }

    private void merge(TextChange existing, TextChange extra) {
        TextEdit extraEdit = extra.getEdit();
        addEdit(existing, extraEdit);
    }

    private void addEdit(TextChange existing, TextEdit extraEdit) {
        if (existing.getEdit() == null) {
            existing.setEdit(new MultiTextEdit());
        }
        Assert.isLegal(existing.getEdit() instanceof MultiTextEdit);
        if (extraEdit instanceof MultiTextEdit) {
            for (TextEdit child : extraEdit.getChildren()) {
                addEdit(existing, child);
            }
        } else {
            existing.addEdit(extraEdit.copy());
        }
    }

    /**
     * This method must be called to process all the text changes, adding changes to
     * files that the refactoring also modifies to the refactoring's changes and
     * removing them from this Change manager.
     * <p>
     * This method should be called from participant's createPreChange method, before
     * requesting the new text changes.
     */
    public void copyExistingChangesTo(RefactoringParticipant participant) {
        Assert.isLegal(this.participant == null || this.participant == participant);
        if (this.participant == null) {
            Assert.isNotNull(participant);
            this.participant = participant;
            Iterator<Object> keys = textChanges.keySet().iterator();
            while (keys.hasNext()) {
                Object key = keys.next();
                TextChange existing = participant.getTextChange(key);
                if (existing != null) {
                    merge(existing, textChanges.get(key));
                    keys.remove();
                }
            }
        }
    }

    public CompositeChange getNewTextChanges() {
        Assert.isLegal(this.participant != null,
                "The method 'copyExistingChangesTo' must be called before this one!");
        if (!textChanges.isEmpty()) {
            boolean addedOne = false;
            CompositeChange composed = new CompositeChange(name + " text changes");
            for (TextChange c : textChanges.values()) {
                if (!isEmpty(c)) {
                    composed.add(c);
                    addedOne = true;
                }
            }
            if (addedOne) {
                return composed;
            }
        }
        return null;
    }

    private boolean isEmpty(TextChange c) {
        TextEdit contents = c.getEdit();
        if (contents == null) {
            return true;
        } else if (contents instanceof MultiTextEdit) {
            return !contents.hasChildren();
        }
        return false;
    }

    public TextChange getCUChange(ICompilationUnit compilationUnit) {
        TextChange existing = getTextChange(compilationUnit);
        if (existing != null) {
            return existing;
        }
        CompilationUnitChange newChange = new CompilationUnitChange(compilationUnit.getElementName(),
                compilationUnit);
        newChange.setEdit(new MultiTextEdit());
        addNewTextChange(newChange);
        return newChange;
    }

    public TextChange getFileChange(IFile file) {
        TextChange existing = getTextChange(file);
        if (existing != null) {
            return existing;
        }
        TextFileChange newChange = new TextFileChange(file.getName(), file);
        newChange.setEdit(new MultiTextEdit());
        addNewTextChange(newChange);
        return newChange;
    }

    public TextChange getTextChangeFor(Object el) {
        if (el instanceof IJavaElement) {
            IJavaElement jel = (IJavaElement) el;
            ICompilationUnit cu = (ICompilationUnit) jel.getAncestor(IJavaElement.COMPILATION_UNIT);
            return getCUChange(cu);
        } else if (el instanceof IFile) {
            return getFileChange((IFile) el);
        }
        return null;
    }
}