/*
* Copyright 2009-2017 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.codehaus.groovy.eclipse.refactoring.actions;
import java.lang.reflect.Array;
import java.util.Comparator;
import java.util.Set;
import java.util.TreeSet;
import org.codehaus.groovy.ast.ASTNode;
import org.codehaus.groovy.eclipse.editor.GroovyEditor;
import org.codehaus.groovy.eclipse.search.GroovyOccurrencesFinder;
import org.eclipse.core.commands.operations.IOperationHistory;
import org.eclipse.core.commands.operations.IUndoContext;
import org.eclipse.core.commands.operations.IUndoableOperation;
import org.eclipse.core.commands.operations.OperationHistoryFactory;
import org.eclipse.jdt.core.IJavaElement;
import org.eclipse.jdt.groovy.core.util.ReflectionUtils;
import org.eclipse.jdt.internal.ui.JavaPlugin;
import org.eclipse.jdt.internal.ui.javaeditor.EditorHighlightingSynchronizer;
import org.eclipse.jdt.internal.ui.refactoring.reorg.RenameLinkedMode;
import org.eclipse.jdt.internal.ui.text.correction.proposals.LinkedNamesAssistProposal.DeleteBlockingExitPolicy;
import org.eclipse.jface.action.IStatusLineManager;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IEditingSupport;
import org.eclipse.jface.text.IEditingSupportRegistry;
import org.eclipse.jface.text.ITextViewerExtension6;
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.IUndoManagerExtension;
import org.eclipse.jface.text.Position;
import org.eclipse.jface.text.link.ILinkedModeListener;
import org.eclipse.jface.text.link.LinkedModeModel;
import org.eclipse.jface.text.link.LinkedModeUI;
import org.eclipse.jface.text.link.LinkedModeUI.ExitFlags;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.link.LinkedPositionGroup;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.Point;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;
/**
* @author andrew
* @created Jan 7, 2011
*/
public class GroovyRenameLinkedMode extends RenameLinkedMode {
// copy from super
private class EditorSynchronizer implements ILinkedModeListener {
public void left(LinkedModeModel model, int flags) {
doLinkedModeLeft();
// don't actually do the refactorings for local variables
// just let the change in text work itself
// this is because doRename wants to use the JDT Rename Temp
// Refactoring
// which will not work for groovy locals
if ((flags & ILinkedModeListener.UPDATE_CARET) != 0 && getJavaElement().getElementType() != IJavaElement.LOCAL_VARIABLE) {
doDoRename(getShowPreview());
}
}
public void resume(LinkedModeModel model, int flags) {}
public void suspend(LinkedModeModel model) {}
}
// copy from super
private class ExitPolicy extends DeleteBlockingExitPolicy {
public ExitPolicy(IDocument document) {
super(document);
}
@Override
public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) {
setShowPreview((event.stateMask & SWT.CTRL) != 0 && (event.character == SWT.CR || event.character == SWT.LF));
return super.doExit(model, event, offset, length);
}
}
private final GroovyEditor editor;
public GroovyRenameLinkedMode(IJavaElement element, GroovyEditor editor) {
super(element, editor);
this.editor = editor;
}
@Override
public void start() {
if (getActiveLinkedMode() != null) {
// for safety; should already be handled in RenameJavaElementAction
getMyActiveLinkedMode().startFullDialog();
return;
}
ISourceViewer viewer = editor.getViewer();
IDocument document = viewer.getDocument();
Point fOriginalSelection = viewer.getSelectedRange();
setOriginalSelection(fOriginalSelection);
int offset = fOriginalSelection.x;
int length = fOriginalSelection.y;
try {
if (viewer instanceof ITextViewerExtension6) {
IUndoManager undoManager = ((ITextViewerExtension6) viewer).getUndoManager();
if (undoManager instanceof IUndoManagerExtension) {
IUndoManagerExtension undoManagerExtension = (IUndoManagerExtension) undoManager;
IUndoContext undoContext = undoManagerExtension.getUndoContext();
IOperationHistory operationHistory = OperationHistoryFactory.getOperationHistory();
setStartingUndoOperation(operationHistory.getUndoOperation(undoContext));
}
}
LinkedPositionGroup fLinkedPositionGroup = new LinkedPositionGroup();
setLinkedPositionGroup(fLinkedPositionGroup);
GroovyOccurrencesFinder finder = new GroovyOccurrencesFinder();
finder.setGroovyCompilationUnit(editor.getGroovyCompilationUnit());
finder.initialize(null, offset, length);
ASTNode nodeToLookFor = finder.getNodeToLookFor();
if (nodeToLookFor == null) {
return;
}
setOriginalName(finder.getElementName());
final int pos = nodeToLookFor.getStart();
Object occurrences = finder.getOccurrences();
if (occurrences == null || Array.getLength(occurrences) == 0) {
return;
}
// convert from array of OccurrenceLocation to ordered collection of Position
Set<Position> positions = new TreeSet<Position>(new Comparator<Position>() {
public int compare(Position p1, Position p2) {
return rank(p1) - rank(p2);
}
/**
* Returns the absolute rank of an <code>ASTNode</code>. Nodes preceding <code>pos</code> are ranked last.
*
* @return the rank of the position with respect to the invocation offset
*/
private int rank(Position p) {
int relativeRank = p.getOffset() + p.getLength() - pos;
if (relativeRank < 0)
return Integer.MAX_VALUE + relativeRank;
return relativeRank;
}
});
int nameLength = finder.getElementName().length();
for (int i = 0, n = Array.getLength(occurrences); i < n; i += 1) {
Object occurrence = Array.get(occurrences, i);
int off = (Integer) ReflectionUtils.getPrivateField(occurrence.getClass(), "fOffset", occurrence),
len = (Integer) ReflectionUtils.getPrivateField(occurrence.getClass(), "fLength", occurrence);
// just in case some source locations are not correct (eg-accessing a getter method as a non-getter property),
// remove ones that do not have the same source length as the original
if (len == nameLength)
positions.add(new Position(off, len));
}
int i = 0;
for (Position position : positions) {
LinkedPosition linkedPosition = new LinkedPosition(document, position.getOffset(), position.getLength(), i);
if (i++ == 0) {
setNamePosition(linkedPosition);
}
fLinkedPositionGroup.addPosition(linkedPosition);
}
// can't do anything if no linked positions are found
if (fLinkedPositionGroup.isEmpty()) {
IStatusLineManager status = getStatusLineManager();
if (status != null) {
status.setErrorMessage("No positions found. Cannot do refactoring...");
}
return;
}
LinkedModeModel fLinkedModeModel = new LinkedModeModel();
setLinkedModeModel(fLinkedModeModel);
fLinkedModeModel.addGroup(fLinkedPositionGroup);
fLinkedModeModel.forceInstall();
fLinkedModeModel.addLinkingListener(new EditorHighlightingSynchronizer(editor));
fLinkedModeModel.addLinkingListener(new EditorSynchronizer());
LinkedModeUI ui = new EditorLinkedModeUI(fLinkedModeModel, viewer);
ui.setExitPosition(viewer, offset, 0, Integer.MAX_VALUE);
ui.setExitPolicy(new ExitPolicy(document));
ui.enter();
// by default, full word is selected; restore original selection
viewer.setSelectedRange(fOriginalSelection.x, fOriginalSelection.y);
if (viewer instanceof IEditingSupportRegistry) {
IEditingSupportRegistry registry = (IEditingSupportRegistry) viewer;
registry.register(getFocusEditingSupport());
}
doOpenSecondaryPopup();
// startAnimation();
setActiveLinkedMode(this);
} catch (BadLocationException e) {
JavaPlugin.log(e);
}
}
private void setOriginalSelection(Point p) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fOriginalSelection", this, p);
}
private void setLinkedPositionGroup(LinkedPositionGroup group) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fLinkedPositionGroup", this, group);
}
private IEditingSupport getFocusEditingSupport() {
return (IEditingSupport) ReflectionUtils.getPrivateField(RenameLinkedMode.class, "fFocusEditingSupport", this);
}
private boolean getShowPreview() {
return (Boolean) ReflectionUtils.getPrivateField(RenameLinkedMode.class, "fShowPreview", this);
}
private IJavaElement getJavaElement() {
return (IJavaElement) ReflectionUtils.getPrivateField(RenameLinkedMode.class, "fJavaElement", this);
}
private static RenameLinkedMode getMyActiveLinkedMode() {
return (RenameLinkedMode) ReflectionUtils.getPrivateField(RenameLinkedMode.class, "fgActiveLinkedMode", null);
}
private void setShowPreview(boolean show) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fShowPreview", this, show);
}
private void setNamePosition(LinkedPosition pos) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fNamePosition", this, pos);
}
private void setStartingUndoOperation(IUndoableOperation op) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fStartingUndoOperation", this, op);
}
private void setLinkedModeModel(LinkedModeModel model) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fLinkedModeModel", this, model);
}
private void setOriginalName(String name) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fOriginalName", this, name);
}
private void setActiveLinkedMode(RenameLinkedMode active) {
ReflectionUtils.setPrivateField(RenameLinkedMode.class, "fgActiveLinkedMode", this, active);
}
private void doOpenSecondaryPopup() {
ReflectionUtils.executeNoArgPrivateMethod(RenameLinkedMode.class, "openSecondaryPopup", this);
}
private void doLinkedModeLeft() {
ReflectionUtils.executeNoArgPrivateMethod(RenameLinkedMode.class, "linkedModeLeft", this);
}
private void doDoRename(boolean showPreview) {
ReflectionUtils.executePrivateMethod(RenameLinkedMode.class, "doRename", new Class[] {boolean.class}, this, new Object[] {showPreview});
}
private IStatusLineManager getStatusLineManager() {
if (editor != null) {
try {
return editor.getEditorSite().getActionBars().getStatusLineManager();
} catch (NullPointerException e) {
// can ignore
}
}
return null;
}
}