/******************************************************************************* * Copyright (c) 2012-2017 Codenvy, S.A. * 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: * Codenvy, S.A. - initial API and implementation *******************************************************************************/ package org.eclipse.che.ide.ext.java.client.refactoring.rename; import com.google.common.base.Optional; import com.google.inject.Inject; import com.google.inject.Singleton; import com.google.web.bindery.event.shared.EventBus; import org.eclipse.che.api.promises.client.Operation; import org.eclipse.che.api.promises.client.OperationException; import org.eclipse.che.api.promises.client.Promise; import org.eclipse.che.api.promises.client.PromiseError; import org.eclipse.che.ide.api.dialogs.DialogFactory; import org.eclipse.che.ide.api.editor.EditorWithAutoSave; import org.eclipse.che.ide.api.editor.document.Document; import org.eclipse.che.ide.api.editor.link.HasLinkedMode; import org.eclipse.che.ide.api.editor.link.LinkedMode; import org.eclipse.che.ide.api.editor.link.LinkedModel; import org.eclipse.che.ide.api.editor.link.LinkedModelData; import org.eclipse.che.ide.api.editor.link.LinkedModelGroup; import org.eclipse.che.ide.api.editor.text.Position; import org.eclipse.che.ide.api.editor.texteditor.TextEditor; import org.eclipse.che.ide.api.editor.texteditor.UndoableEditor; import org.eclipse.che.ide.api.event.FileEvent; import org.eclipse.che.ide.api.event.FileEvent.FileEventHandler; import org.eclipse.che.ide.api.event.ng.ClientServerEventService; import org.eclipse.che.ide.api.notification.NotificationManager; import org.eclipse.che.ide.api.resources.Project; import org.eclipse.che.ide.api.resources.Resource; import org.eclipse.che.ide.api.resources.VirtualFile; import org.eclipse.che.ide.dto.DtoFactory; import org.eclipse.che.ide.ext.java.client.JavaLocalizationConstant; import org.eclipse.che.ide.ext.java.client.refactoring.RefactoringUpdater; import org.eclipse.che.ide.ext.java.client.refactoring.rename.wizard.RenamePresenter; import org.eclipse.che.ide.ext.java.client.refactoring.service.RefactoringServiceClient; import org.eclipse.che.ide.ext.java.client.util.JavaUtil; import org.eclipse.che.ide.ext.java.shared.dto.LinkedData; import org.eclipse.che.ide.ext.java.shared.dto.LinkedModeModel; import org.eclipse.che.ide.ext.java.shared.dto.LinkedPositionGroup; import org.eclipse.che.ide.ext.java.shared.dto.Region; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.ChangeInfo; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.CreateRenameRefactoring; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.LinkedRenameRefactoringApply; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringResult; import org.eclipse.che.ide.ext.java.shared.dto.refactoring.RenameRefactoringSession; import javax.validation.constraints.NotNull; import java.util.ArrayList; import java.util.List; import static org.eclipse.che.ide.api.event.FileEvent.FileOperation.CLOSE; import static org.eclipse.che.ide.api.notification.StatusNotification.DisplayMode.FLOAT_MODE; import static org.eclipse.che.ide.api.notification.StatusNotification.Status.FAIL; import static org.eclipse.che.ide.ext.java.shared.dto.refactoring.CreateRenameRefactoring.RenameType.JAVA_ELEMENT; import static org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringStatus.ERROR; import static org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringStatus.FATAL; import static org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringStatus.INFO; import static org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringStatus.OK; import static org.eclipse.che.ide.ext.java.shared.dto.refactoring.RefactoringStatus.WARNING; /** * Class for rename refactoring java classes * * @author Alexander Andrienko * @author Valeriy Svydenko */ @Singleton public class JavaRefactoringRename implements FileEventHandler { private final RenamePresenter renamePresenter; private final RefactoringUpdater refactoringUpdater; private final JavaLocalizationConstant locale; private final RefactoringServiceClient refactoringServiceClient; private final DtoFactory dtoFactory; private final ClientServerEventService clientServerEventService; private final DialogFactory dialogFactory; private final NotificationManager notificationManager; private boolean isActiveLinkedEditor; private TextEditor textEditor; private LinkedMode mode; private HasLinkedMode linkedEditor; private String newName; @Inject public JavaRefactoringRename(RenamePresenter renamePresenter, RefactoringUpdater refactoringUpdater, JavaLocalizationConstant locale, RefactoringServiceClient refactoringServiceClient, ClientServerEventService clientServerEventService, DtoFactory dtoFactory, EventBus eventBus, DialogFactory dialogFactory, NotificationManager notificationManager) { this.renamePresenter = renamePresenter; this.refactoringUpdater = refactoringUpdater; this.locale = locale; this.clientServerEventService = clientServerEventService; this.dialogFactory = dialogFactory; this.refactoringServiceClient = refactoringServiceClient; this.dtoFactory = dtoFactory; this.notificationManager = notificationManager; isActiveLinkedEditor = false; eventBus.addHandler(FileEvent.TYPE, this); } /** * Launch java rename refactoring process * * @param textEditorPresenter * editor where user makes refactoring */ public void refactor(final TextEditor textEditorPresenter) { if (!(textEditorPresenter instanceof HasLinkedMode)) { return; } if (isActiveLinkedEditor) { createRenameSession(); } else { textEditor = textEditorPresenter; createLinkedRenameSession(); } isActiveLinkedEditor = !isActiveLinkedEditor; linkedEditor = (HasLinkedMode)textEditorPresenter; textEditorPresenter.setFocus(); } private void createRenameSession() { final CreateRenameRefactoring refactoringSession = createRenameRefactoringDto(textEditor, false); Promise<RenameRefactoringSession> createRenamePromise = refactoringServiceClient.createRenameRefactoring(refactoringSession); createRenamePromise.then(new Operation<RenameRefactoringSession>() { @Override public void apply(RenameRefactoringSession session) throws OperationException { renamePresenter.show(session); if (mode != null) { mode.exitLinkedMode(false); } } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError arg) throws OperationException { showError(); } }); } private void showError() { dialogFactory.createMessageDialog(locale.renameRename(), locale.renameOperationUnavailable(), null).show(); if (mode != null) { mode.exitLinkedMode(false); } } private void createLinkedRenameSession() { final CreateRenameRefactoring refactoringSession = createRenameRefactoringDto(textEditor, true); Promise<RenameRefactoringSession> createRenamePromise = refactoringServiceClient.createRenameRefactoring(refactoringSession); createRenamePromise.then(new Operation<RenameRefactoringSession>() { @Override public void apply(RenameRefactoringSession session) throws OperationException { clientServerEventService.sendFileTrackingSuspendEvent().then(success -> { activateLinkedModeIntoEditor(session, textEditor.getDocument()); }); } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError arg) throws OperationException { isActiveLinkedEditor = false; showError(); } }); } @Override public void onFileOperation(FileEvent event) { if (event.getOperationType() == CLOSE && textEditor != null && textEditor.getDocument() != null && textEditor.getDocument().getFile().getLocation().equals(event.getFile().getLocation())) { isActiveLinkedEditor = false; } } /** returns {@code true} if linked editor is activated. */ public boolean isActiveLinkedEditor() { return isActiveLinkedEditor; } private void activateLinkedModeIntoEditor(final RenameRefactoringSession session, final Document document) { mode = linkedEditor.getLinkedMode(); LinkedModel model = linkedEditor.createLinkedModel(); LinkedModeModel linkedModeModel = session.getLinkedModeModel(); List<LinkedModelGroup> groups = new ArrayList<>(); for (LinkedPositionGroup positionGroup : linkedModeModel.getGroups()) { LinkedModelGroup group = linkedEditor.createLinkedGroup(); LinkedData data = positionGroup.getData(); if (data != null) { LinkedModelData modelData = linkedEditor.createLinkedModelData(); modelData.setType("link"); modelData.setValues(data.getValues()); group.setData(modelData); } List<Position> positions = new ArrayList<>(); for (Region region : positionGroup.getPositions()) { positions.add(new Position(region.getOffset(), region.getLength())); } group.setPositions(positions); groups.add(group); } model.setGroups(groups); disableAutoSave(); mode.enterLinkedMode(model); mode.addListener(new LinkedMode.LinkedModeListener() { @Override public void onLinkedModeExited(boolean successful, int start, int end) { boolean isSuccessful = false; try { if (successful) { isSuccessful = true; newName = document.getContentRange(start, end - start); performRename(session); } } finally { mode.removeListener(this); isActiveLinkedEditor = false; if (!isSuccessful && linkedEditor instanceof EditorWithAutoSave) { ((EditorWithAutoSave)linkedEditor).enableAutoSave(); } } } }); } private void performRename(RenameRefactoringSession session) { final LinkedRenameRefactoringApply dto = createLinkedRenameRefactoringApplyDto(newName, session.getSessionId()); Promise<RefactoringResult> applyModelPromise = refactoringServiceClient.applyLinkedModeRename(dto); applyModelPromise.then(new Operation<RefactoringResult>() { @Override public void apply(RefactoringResult result) throws OperationException { switch (result.getSeverity()) { case OK: case INFO: List<ChangeInfo> changes = result.getChanges(); refactoringUpdater.updateAfterRefactoring(changes).then(arg -> { final VirtualFile file = textEditor.getDocument().getFile(); if (file instanceof Resource) { final Optional<Project> project = ((Resource)file).getRelatedProject(); refactoringServiceClient.reindexProject(project.get().getLocation().toString()); } enableAutoSave(); refactoringUpdater.handleMovingFiles(changes).then(clientServerEventService.sendFileTrackingResumeEvent()); }); break; case WARNING: case ERROR: enableAutoSave(); undoChanges(); showWarningDialog(); break; case FATAL: undoChanges(); clientServerEventService.sendFileTrackingResumeEvent(); notificationManager.notify(locale.failedToRename(), result.getEntries().get(0).getMessage(), FAIL, FLOAT_MODE); break; default: break; } } }).catchError(new Operation<PromiseError>() { @Override public void apply(PromiseError arg) throws OperationException { enableAutoSave(); undoChanges(); notificationManager.notify(locale.failedToRename(), arg.getMessage(), FAIL, FLOAT_MODE); } }); } private void enableAutoSave() { if (linkedEditor instanceof EditorWithAutoSave) { ((EditorWithAutoSave)linkedEditor).enableAutoSave(); } } private void disableAutoSave() { if (linkedEditor instanceof EditorWithAutoSave) { ((EditorWithAutoSave)linkedEditor).disableAutoSave(); } } private void undoChanges() { if (linkedEditor instanceof UndoableEditor) { ((UndoableEditor)linkedEditor).getUndoRedo().undo(); } } private void showWarningDialog() { dialogFactory.createConfirmDialog(locale.warningOperationTitle(), locale.renameWithWarnings(), locale.showRenameWizard(), locale.buttonCancel(), () -> { isActiveLinkedEditor = true; refactor(textEditor); isActiveLinkedEditor = false; }, clientServerEventService::sendFileTrackingResumeEvent).show(); } @NotNull private CreateRenameRefactoring createRenameRefactoringDto(TextEditor editor, boolean isActiveLinkedMode) { CreateRenameRefactoring dto = dtoFactory.createDto(CreateRenameRefactoring.class); dto.setOffset(editor.getCursorOffset()); dto.setRefactorLightweight(isActiveLinkedMode); final VirtualFile file = editor.getEditorInput().getFile(); dto.setPath(JavaUtil.resolveFQN(file)); if (file instanceof Resource) { final Optional<Project> project = ((Resource)file).getRelatedProject(); dto.setProjectPath(project.get().getLocation().toString()); } dto.setType(JAVA_ELEMENT); return dto; } @NotNull private LinkedRenameRefactoringApply createLinkedRenameRefactoringApplyDto(String newName, String sessionId) { LinkedRenameRefactoringApply dto = dtoFactory.createDto(LinkedRenameRefactoringApply.class); dto.setNewName(newName); dto.setSessionId(sessionId); return dto; } }