/******************************************************************************* * 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 * http://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; } }