com.cisco.yangide.ext.refactoring.ui.RenameLinkedMode.java Source code

Java tutorial

Introduction

Here is the source code for com.cisco.yangide.ext.refactoring.ui.RenameLinkedMode.java

Source

/*******************************************************************************
 * Copyright (c) 2014, 2015 Cisco Systems, Inc. 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 com.cisco.yangide.ext.refactoring.ui;

import java.lang.reflect.InvocationTargetException;
import java.util.Arrays;
import java.util.Comparator;

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.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.jface.operation.IRunnableWithProgress;
import org.eclipse.jface.text.BadLocationException;
import org.eclipse.jface.text.DocumentEvent;
import org.eclipse.jface.text.IDocument;
import org.eclipse.jface.text.IEditingSupport;
import org.eclipse.jface.text.IEditingSupportRegistry;
import org.eclipse.jface.text.IRegion;
import org.eclipse.jface.text.ITextViewerExtension6;
import org.eclipse.jface.text.IUndoManager;
import org.eclipse.jface.text.IUndoManagerExtension;
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.LinkedModeUI.IExitPolicy;
import org.eclipse.jface.text.link.LinkedPosition;
import org.eclipse.jface.text.link.LinkedPositionGroup;
import org.eclipse.jface.text.source.ISourceViewer;
import org.eclipse.jface.text.source.SourceViewer;
import org.eclipse.swt.SWT;
import org.eclipse.swt.custom.StyledText;
import org.eclipse.swt.events.VerifyEvent;
import org.eclipse.swt.graphics.GC;
import org.eclipse.swt.graphics.Image;
import org.eclipse.swt.graphics.Point;
import org.eclipse.swt.widgets.Composite;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;
import org.eclipse.swt.widgets.Label;
import org.eclipse.swt.widgets.Shell;
import org.eclipse.ui.texteditor.link.EditorLinkedModeUI;

import com.cisco.yangide.core.YangModelException;
import com.cisco.yangide.core.dom.ASTNamedNode;
import com.cisco.yangide.editor.editors.YangEditor;
import com.cisco.yangide.ext.refactoring.YangRefactoringPlugin;
import com.cisco.yangide.ext.refactoring.actions.RenameSupport;

/**
 * @author Konstantin Zaitsev
 * @date Aug 4, 2014
 */
public class RenameLinkedMode {

    private class FocusEditingSupport implements IEditingSupport {
        @Override
        public boolean ownsFocusShell() {
            if (fInfoPopup == null) {
                return false;
            }
            if (fInfoPopup.ownsFocusShell()) {
                return true;
            }

            Shell editorShell = editor.getSite().getShell();
            Shell activeShell = editorShell.getDisplay().getActiveShell();
            if (editorShell == activeShell) {
                return true;
            }
            return false;
        }

        @Override
        public boolean isOriginator(DocumentEvent event, IRegion subjectRegion) {
            return false; // leave on external modification outside positions
        }
    }

    private class EditorSynchronizer implements ILinkedModeListener {
        @Override
        public void left(LinkedModeModel model, int flags) {
            linkedModeLeft();
            if ((flags & ILinkedModeListener.UPDATE_CARET) != 0) {
                doRename(fShowPreview);
            }
        }

        @Override
        public void resume(LinkedModeModel model, int flags) {
        }

        @Override
        public void suspend(LinkedModeModel model) {
        }
    }

    private class ExitPolicy implements IExitPolicy {
        private IDocument document;

        public ExitPolicy(IDocument document) {
            this.document = document;
        }

        @Override
        public ExitFlags doExit(LinkedModeModel model, VerifyEvent event, int offset, int length) {
            fShowPreview = (event.stateMask & SWT.CTRL) != 0
                    && (event.character == SWT.CR || event.character == SWT.LF);
            if (length == 0 && (event.character == SWT.BS || event.character == SWT.DEL)) {
                LinkedPosition position = model
                        .findPosition(new LinkedPosition(document, offset, 0, LinkedPositionGroup.NO_STOP));
                if (position != null) {
                    if (event.character == SWT.BS) {
                        if (offset - 1 < position.getOffset()) {
                            // skip backspace at beginning of linked position
                            event.doit = false;
                        }
                    } else /* event.character == SWT.DEL */ {
                        if (offset + 1 > position.getOffset() + position.getLength()) {
                            // skip delete at end of linked position
                            event.doit = false;
                        }
                    }
                }
            }

            return null; // don't change behavior
        }
    }

    private static RenameLinkedMode fgActiveLinkedMode;

    private final YangEditor editor;
    /** Original definition node */
    private final ASTNamedNode originalNode;

    private RenameInformationPopup fInfoPopup;

    private Point fOriginalSelection;
    private String fOriginalName;
    /** Selected in editor node. */
    private ASTNamedNode selectedNode;

    private LinkedPosition fNamePosition;
    private LinkedModeModel fLinkedModeModel;
    private LinkedPositionGroup fLinkedPositionGroup;
    private final FocusEditingSupport fFocusEditingSupport;
    private boolean fShowPreview;
    private IUndoableOperation fStartingUndoOperation;

    private IFile originalFile;

    public RenameLinkedMode(ASTNamedNode originalNode, IFile originalFile, ASTNamedNode selectedNode,
            YangEditor editor) {
        this.editor = editor;
        this.originalFile = originalFile;
        this.originalNode = originalNode;
        this.selectedNode = selectedNode;
        fFocusEditingSupport = new FocusEditingSupport();
    }

    public static RenameLinkedMode getActiveLinkedMode() {
        if (fgActiveLinkedMode != null) {
            ISourceViewer viewer = fgActiveLinkedMode.editor.getViewer();
            if (viewer != null) {
                StyledText textWidget = viewer.getTextWidget();
                if (textWidget != null && !textWidget.isDisposed()) {
                    return fgActiveLinkedMode;
                }
            }
            // make sure we don't hold onto the active linked mode if anything went wrong with
            // canceling:
            fgActiveLinkedMode = null;
        }
        return null;
    }

    public void start() {
        if (getActiveLinkedMode() != null) {
            // for safety; should already be handled in RenameJavaElementAction
            fgActiveLinkedMode.startFullDialog();
            return;
        }

        ISourceViewer viewer = editor.getViewer();
        IDocument document = viewer.getDocument();
        fOriginalSelection = viewer.getSelectedRange();
        int offset = fOriginalSelection.x;

        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();
                    fStartingUndoOperation = operationHistory.getUndoOperation(undoContext);
                }
            }

            fOriginalName = selectedNode.getName();
            if (fOriginalName.indexOf(':') > 0) {
                fOriginalName = fOriginalName.substring(fOriginalName.indexOf(':'), fOriginalName.length());
            }
            final int pos = selectedNode.getNameStartPosition();
            fLinkedPositionGroup = new LinkedPositionGroup();

            ASTNamedNode[] sameNodes = RenameSupport.findLocalReferences(editor.getModule(), originalNode);

            Arrays.sort(sameNodes, new Comparator<ASTNamedNode>() {
                @Override
                public int compare(ASTNamedNode o1, ASTNamedNode o2) {
                    return rank(o1) - rank(o2);
                }

                /**
                 * Returns the absolute rank of an <code>ASTNode</code>. Nodes preceding
                 * <code>pos</code> are ranked last.
                 *
                 * @param node the node to compute the rank for
                 * @return the rank of the node with respect to the invocation offset
                 */
                private int rank(ASTNamedNode node) {
                    int relativeRank = node.getNameStartPosition() + node.getNameLength() - pos;
                    if (relativeRank < 0) {
                        return Integer.MAX_VALUE + relativeRank;
                    } else {
                        return relativeRank;
                    }
                }
            });
            for (int i = 0; i < sameNodes.length; i++) {
                ASTNamedNode elem = sameNodes[i];
                String name = elem.getName();
                int elPrefixOffset = name.indexOf(':');
                int elPos = elem.getNameStartPosition();
                int elLength = elem.getNameLength();

                // normalize quote
                if (name.length() + 2 == elLength) {
                    elPos++;
                    elLength -= 2;
                }
                // normalize prefixes
                if (elPrefixOffset > 0) {
                    elPos += elPrefixOffset + 1;
                    elLength = elLength - elPrefixOffset - 1;
                }
                LinkedPosition linkedPosition = new LinkedPosition(document, elPos, elLength, i);
                if (i == 0) {
                    fNamePosition = linkedPosition;
                }
                fLinkedPositionGroup.addPosition(linkedPosition);
            }

            fLinkedModeModel = new LinkedModeModel();
            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(fFocusEditingSupport);
            }

            openSecondaryPopup();
            // startAnimation();
            fgActiveLinkedMode = this;

        } catch (BadLocationException | YangModelException e) {
            YangRefactoringPlugin.log(e);
        }
    }

    void doRename(boolean showPreview) {
        cancel();

        Image image = null;
        Label label = null;

        fShowPreview |= showPreview;
        try {
            ISourceViewer viewer = editor.getViewer();
            if (viewer instanceof SourceViewer) {
                SourceViewer sourceViewer = (SourceViewer) viewer;
                Control viewerControl = sourceViewer.getControl();
                if (viewerControl instanceof Composite) {
                    Composite composite = (Composite) viewerControl;
                    Display display = composite.getDisplay();

                    // Flush pending redraw requests:
                    while (!display.isDisposed() && display.readAndDispatch()) {
                    }

                    // Copy editor area:
                    GC gc = new GC(composite);
                    Point size;
                    try {
                        size = composite.getSize();
                        image = new Image(gc.getDevice(), size.x, size.y);
                        gc.copyArea(image, 0, 0);
                    } finally {
                        gc.dispose();
                        gc = null;
                    }

                    // Persist editor area while executing refactoring:
                    label = new Label(composite, SWT.NONE);
                    label.setImage(image);
                    label.setBounds(0, 0, size.x, size.y);
                    label.moveAbove(null);
                }
            }

            String newName = fNamePosition.getContent();
            if (fOriginalName.equals(newName)) {
                return;
            }
            RenameSupport renameSupport = undoAndCreateRenameSupport(newName);
            if (renameSupport == null) {
                return;
            }

            Shell shell = editor.getSite().getShell();
            boolean executed;
            if (fShowPreview) {
                executed = renameSupport.openDialog(shell, true);
            } else {
                renameSupport.perform(shell, editor.getSite().getWorkbenchWindow());
                executed = true;
            }
            if (executed) {
                restoreFullSelection();
            }
            editor.reconcileModel();
        } catch (BadLocationException | CoreException e) {
            YangRefactoringPlugin.log(e);
        } finally {
            if (label != null) {
                label.dispose();
            }
            if (image != null) {
                image.dispose();
            }
        }
    }

    public void cancel() {
        if (fLinkedModeModel != null) {
            fLinkedModeModel.exit(ILinkedModeListener.NONE);
        }
        linkedModeLeft();
    }

    private void restoreFullSelection() {
        if (fOriginalSelection.y != 0) {
            int originalOffset = fOriginalSelection.x;
            LinkedPosition[] positions = fLinkedPositionGroup.getPositions();
            for (int i = 0; i < positions.length; i++) {
                LinkedPosition position = positions[i];
                if (!position.isDeleted() && position.includes(originalOffset)) {
                    editor.getViewer().setSelectedRange(position.offset, position.length);
                    return;
                }
            }
        }
    }

    private RenameSupport undoAndCreateRenameSupport(String newName) throws CoreException {
        // Assumption: the linked mode model should be shut down by now.

        final ISourceViewer viewer = editor.getViewer();

        try {
            if (!fOriginalName.equals(newName)) {
                editor.getSite().getWorkbenchWindow().run(false, true, new IRunnableWithProgress() {
                    @Override
                    public void run(IProgressMonitor monitor)
                            throws InvocationTargetException, InterruptedException {
                        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();
                                while (undoManager.undoable()) {
                                    if (fStartingUndoOperation != null && fStartingUndoOperation
                                            .equals(operationHistory.getUndoOperation(undoContext))) {
                                        return;
                                    }
                                    undoManager.undo();
                                }
                            }
                        }
                    }
                });
            }
        } catch (InvocationTargetException e) {
            throw new CoreException(
                    new Status(IStatus.ERROR, YangRefactoringPlugin.PLUGIN_ID, "Error saving editor", e));
        } catch (InterruptedException e) {
            // canceling is OK
            return null;
        } finally {
            editor.reconcileModel();
        }

        viewer.setSelectedRange(fOriginalSelection.x, fOriginalSelection.y);

        if (newName.length() == 0) {
            return null;
        }
        return new RenameSupport(originalFile, originalNode, newName);
    }

    public void startFullDialog() {
        cancel();

        try {
            String newName = fNamePosition.getContent();
            RenameSupport renameSupport = undoAndCreateRenameSupport(newName);
            if (renameSupport != null) {
                renameSupport.openDialog(editor.getSite().getShell());
            }
        } catch (BadLocationException | CoreException e) {
            YangRefactoringPlugin.log(e);
        }
    }

    private void linkedModeLeft() {
        fgActiveLinkedMode = null;
        if (fInfoPopup != null) {
            fInfoPopup.close();
        }

        ISourceViewer viewer = editor.getViewer();
        if (viewer instanceof IEditingSupportRegistry) {
            IEditingSupportRegistry registry = (IEditingSupportRegistry) viewer;
            registry.unregister(fFocusEditingSupport);
        }
    }

    private void openSecondaryPopup() {
        fInfoPopup = new RenameInformationPopup(editor, this);
        fInfoPopup.open();
    }

    public boolean isCaretInLinkedPosition() {
        return getCurrentLinkedPosition() != null;
    }

    public LinkedPosition getCurrentLinkedPosition() {
        Point selection = editor.getViewer().getSelectedRange();
        int start = selection.x;
        int end = start + selection.y;
        LinkedPosition[] positions = fLinkedPositionGroup.getPositions();
        for (int i = 0; i < positions.length; i++) {
            LinkedPosition position = positions[i];
            if (position.includes(start) && position.includes(end)) {
                return position;
            }
        }
        return null;
    }

    public boolean isOriginalName() {
        try {
            String newName = fNamePosition.getContent();
            return fOriginalName.equals(newName);
        } catch (BadLocationException e) {
            return false;
        }
    }

}