/*******************************************************************************
* 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.ui;
import java.util.ArrayList;
import java.util.List;
import org.codehaus.groovy.eclipse.refactoring.actions.IRenameTarget;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IOperationHistoryListener;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.OperationHistoryEvent;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.core.runtime.Assert;
import org.eclipse.jdt.core.ICompilationUnit;
import org.eclipse.jdt.core.IType;
import org.eclipse.jdt.core.JavaModelException;
import org.eclipse.jdt.internal.ui.javaeditor.JavaEditor;
import org.eclipse.jface.text.ITextViewerExtension6;
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.IUndoManagerExtension;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.ltk.ui.refactoring.RefactoringWizardOpenOperation;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.texteditor.AbstractTextEditor;
import org.grails.ide.eclipse.core.GrailsCoreActivator;
import org.grails.ide.eclipse.editor.groovy.elements.IGrailsElement;
import org.grails.ide.eclipse.refactoring.rename.type.GrailsTypeRenameRefactoring;
/**
* @author Kris De Volder
* @since 2.7
*/
public class GrailsTypeRenameTarget implements IRenameTarget {
private static final boolean DEBUG = false;
private static void debug(String string) {
if (DEBUG) {
System.out.println(string);
}
}
/**
* This listener is active while we are performing a refactoring action triggered from the
* editor and its purpose is to add the editor as an undo context to the operations that
* were triggered from the editor. Also it 'deletes' bogus undos that were added because
* of https://bugs.eclipse.org/bugs/show_bug.cgi?id=345342
* <p>
* Normally, we shouldn't have to do any of this, and presumably this stuff will be obsolete
* if the bug gets fixed.
*/
public class OperationListener implements IOperationHistoryListener {
/**
* This collects the bogus undo operations added because of bug:
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=345342
*/
private List<IUndoableOperation> bogusUndos = new ArrayList<IUndoableOperation>();
public OperationListener() {
OperationHistoryFactory.getOperationHistory().addOperationHistoryListener(this);
}
public void historyNotification(OperationHistoryEvent event) {
if (event.getEventType() == OperationHistoryEvent.OPERATION_ADDED) {
IUndoableOperation oper = event.getOperation();
debug("Undoable operation : " + oper);
if (isBogus(oper)) {
debug("bogus undo detected and remembered!");
bogusUndos.add(oper);
} else {
IUndoContext context = getUndoContext();
if (context!=null) {
oper.addContext(getUndoContext());
}
}
}
}
/**
* Recognizes the bogus undos added because of bug
* https://bugs.eclipse.org/bugs/show_bug.cgi?id=345342
*/
private boolean isBogus(IUndoableOperation oper) {
return oper.toString().contains("org.eclipse.text.undo.DocumentUndoManager$UndoableTextChange undo modification stamp: -1 ");
}
public void dispose() {
OperationHistoryFactory.getOperationHistory().removeOperationHistoryListener(this);
removeBogusUndos();
}
/**
* Work around for bug https://bugs.eclipse.org/bugs/show_bug.cgi?id=345342
*/
private void removeBogusUndos() {
IOperationHistory hist = OperationHistoryFactory.getOperationHistory();
final IUndoableOperation[] NO_UNDOS = new IUndoableOperation[0];
for (IUndoableOperation bogusUndo : bogusUndos) {
debug("deleting bogus undo: "+bogusUndo);
IUndoContext[] contexts = bogusUndo.getContexts();
for (IUndoContext c : contexts) {
bogusUndo.removeContext(c);
// Deleting all contexts makes the undo as good as removed since it can't be triggered from any context now.
}
}
}
}
private IGrailsElement targetElement;
private ICompilationUnit cu;
private IType type;
private IUndoContext undoContext;
private IUndoContext getUndoContext() {
return this.undoContext;
}
public GrailsTypeRenameTarget(IGrailsElement target) throws JavaModelException {
//TODO: stuff in here can go wrong and we aren't handling it
this.targetElement = target;
this.cu = target.getCompilationUnit();
this.type = cu.getAllTypes()[0];
}
private void initUndoContext(AbstractTextEditor _editor) {
if (_editor instanceof JavaEditor) {
JavaEditor editor = (JavaEditor) _editor;
ISourceViewer viewer = editor.getViewer();
// IDocument document= viewer.getDocument();
// fOriginalSelection= viewer.getSelectedRange();
// int offset= fOriginalSelection.x;
try {
// CompilationUnit root= SharedASTProvider.getAST(getCompilationUnit(), SharedASTProvider.WAIT_YES, null);
// fLinkedPositionGroup= new LinkedPositionGroup();
// ASTNode selectedNode= NodeFinder.perform(root, fOriginalSelection.x, fOriginalSelection.y);
// if (! (selectedNode instanceof SimpleName)) {
// return; // TODO: show dialog
// }
// SimpleName nameNode= (SimpleName) selectedNode;
if (viewer instanceof ITextViewerExtension6) {
IUndoManager undoManager= ((ITextViewerExtension6)viewer).getUndoManager();
if (undoManager instanceof IUndoManagerExtension) {
IUndoManagerExtension undoManagerExtension= (IUndoManagerExtension)undoManager;
this.undoContext = undoManagerExtension.getUndoContext();
// IOperationHistory operationHistory= OperationHistoryFactory.getOperationHistory();
// fStartingUndoOperation= operationHistory.getUndoOperation(undoContext);
}
}
} catch (Exception e) {
GrailsCoreActivator.log(e);
}
}
}
public boolean performRenameAction(Shell shell, AbstractTextEditor editor, boolean lightweight) {
Assert.isLegal(undoContext==null, "Nested or concurrent 'performRenameAction' is not allowed");
OperationListener operationListener = null;
initUndoContext(editor);
try {
GrailsTypeRenameRefactoring refactoring = new GrailsTypeRenameRefactoring(type);
GrailsTypeRenameWizard wizard = new GrailsTypeRenameWizard(refactoring);
if (getUndoContext()!=null) {
operationListener = new OperationListener();
}
try {
RefactoringWizardOpenOperation operation= new RefactoringWizardOpenOperation(wizard);
operation.run(shell, refactoring.getName());
} catch (InterruptedException exception) {
Thread.currentThread().interrupt();
}
return true;
} catch (Exception e) {
GrailsCoreActivator.log(e);
return false;
} finally {
undoContext = null;
if (operationListener!=null) {
operationListener.dispose();
}
}
}
}