/*******************************************************************************
* 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;
import com.google.gwt.core.client.Scheduler;
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.Promise;
import org.eclipse.che.api.promises.client.PromiseProvider;
import org.eclipse.che.ide.api.app.AppContext;
import org.eclipse.che.ide.api.editor.EditorAgent;
import org.eclipse.che.ide.api.editor.EditorPartPresenter;
import org.eclipse.che.ide.api.event.FileContentUpdateEvent;
import org.eclipse.che.ide.api.event.ng.ClientServerEventService;
import org.eclipse.che.ide.api.event.ng.DeletedFilesController;
import org.eclipse.che.ide.api.parts.PartPresenter;
import org.eclipse.che.ide.api.parts.WorkspaceAgent;
import org.eclipse.che.ide.api.resources.ExternalResourceDelta;
import org.eclipse.che.ide.api.resources.ModificationTracker;
import org.eclipse.che.ide.api.resources.ResourceDelta;
import org.eclipse.che.ide.api.resources.VirtualFile;
import org.eclipse.che.ide.ext.java.shared.dto.refactoring.ChangeInfo;
import org.eclipse.che.ide.part.editor.multipart.EditorMultiPartStackPresenter;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.resources.reveal.RevealResourceEvent;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static com.google.common.base.Strings.isNullOrEmpty;
import static java.util.stream.Collectors.toList;
import static org.eclipse.che.ide.api.resources.ResourceDelta.ADDED;
import static org.eclipse.che.ide.api.resources.ResourceDelta.MOVED_FROM;
import static org.eclipse.che.ide.api.resources.ResourceDelta.MOVED_TO;
/**
* Utility class for the refactoring operations.
* It is needed for refreshing the project tree, updating content of the opening editors.
*
* @author Valeriy Svydenko
* @author Vlad Zhukovskyi
*/
@Singleton
public class RefactoringUpdater {
private final AppContext appContext;
private final EditorMultiPartStackPresenter editorMultiPartStack;
private final WorkspaceAgent workspaceAgent;
private final DeletedFilesController deletedFilesController;
private final EventBus eventBus;
private final EditorAgent editorAgent;
private final ClientServerEventService clientServerEventService;
private final PromiseProvider promises;
@Inject
public RefactoringUpdater(AppContext appContext,
EditorMultiPartStackPresenter editorMultiPartStack,
WorkspaceAgent workspaceAgent,
DeletedFilesController deletedFilesController,
EventBus eventBus,
EditorAgent editorAgent,
ClientServerEventService clientServerEventService,
PromiseProvider promises) {
this.appContext = appContext;
this.editorMultiPartStack = editorMultiPartStack;
this.workspaceAgent = workspaceAgent;
this.deletedFilesController = deletedFilesController;
this.eventBus = eventBus;
this.editorAgent = editorAgent;
this.clientServerEventService = clientServerEventService;
this.promises = promises;
}
/**
* Iterates over each refactoring change and according to change type performs specific update operation.
* i.e. for {@code ChangeName#UPDATE} updates only opened editors, for {@code ChangeName#MOVE or ChangeName#RENAME_COMPILATION_UNIT}
* updates only new paths and opened editors, for {@code ChangeName#RENAME_PACKAGE} reloads package structure and restore expansion.
*
* @param changes
* applied changes
*/
public Promise<Void> updateAfterRefactoring(List<ChangeInfo> changes) {
if (changes == null || changes.isEmpty()) {
return promises.resolve(null);
}
ExternalResourceDelta[] deltas = new ExternalResourceDelta[0];
final List<String> pathChanged = new ArrayList<>();
for (ChangeInfo change : changes) {
final ExternalResourceDelta delta;
final Path newPath = Path.valueOf(change.getPath());
final Path oldPath = !isNullOrEmpty(change.getOldPath()) ? Path.valueOf(change.getOldPath()) : Path.EMPTY;
switch (change.getName()) {
case MOVE:
case RENAME_COMPILATION_UNIT:
delta = new ExternalResourceDelta(newPath, oldPath, ADDED | MOVED_FROM | MOVED_TO);
registerRemovedFile(change);
break;
case RENAME_PACKAGE:
delta = new ExternalResourceDelta(newPath, oldPath, ADDED | MOVED_FROM | MOVED_TO);
break;
case UPDATE:
if (!isFileRemoved(change.getPath(), changes)) {
pathChanged.add(change.getPath());
registerRemovedFile(change);
}
default:
continue;
}
final int index = deltas.length;
deltas = Arrays.copyOf(deltas, index + 1);
deltas[index] = delta;
}
//here we need to remove file for file that moved or renamed JDT lib sent it to
for (int i = 0; i < deltas.length; i++) {
if (pathChanged.contains(deltas[i].getToPath().toString())) {
pathChanged.remove(deltas[i].getToPath().toString());
}
if (pathChanged.contains(deltas[i].getFromPath().toString())) {
pathChanged.remove(deltas[i].getFromPath().toString());
}
}
if (deltas.length > 0) {
List<EditorPartPresenter> editorsToUpdate = editorAgent.getOpenedEditors().stream()
.filter(editor -> {
VirtualFile file = editor.getEditorInput().getFile();
String editorPath = file.getLocation().toString();
return pathChanged.contains(editorPath);
})
.collect(toList());
return appContext.getWorkspaceRoot().synchronize(deltas).then(appliedDeltas -> {
for (ResourceDelta delta : appliedDeltas) {
eventBus.fireEvent(new RevealResourceEvent(delta.getToPath()));
}
}).then(updateEditors(editorsToUpdate));
} else {
return updateEditors(editorAgent.getOpenedEditors());
}
}
private Promise<Void> updateEditors(List<EditorPartPresenter> editorsToUpdate) {
return promises.create(callback -> Scheduler.get().scheduleDeferred(() -> {
editorsToUpdate.forEach(editor -> updateFileContent(editor.getEditorInput().getFile()));
setActiveEditor();
callback.onSuccess(null);
}));
}
private void updateFileContent(VirtualFile virtualFile) {
String path = virtualFile.getLocation().toString();
if (virtualFile instanceof ModificationTracker) {
String modificationStamp = ((ModificationTracker)virtualFile).getModificationStamp();
eventBus.fireEvent(new FileContentUpdateEvent(path, modificationStamp));
return;
}
eventBus.fireEvent(new FileContentUpdateEvent(path));
}
public Promise<Void> handleMovingFiles(List<ChangeInfo> changes) {
changes.stream()
.filter(change -> !isNullOrEmpty(change.getOldPath()))
.forEach(change -> {
String path = change.getPath();
String oldPath = change.getOldPath();
clientServerEventService.sendFileTrackingMoveEvent(path, oldPath);
});
return promises.resolve(null);
}
private void registerRemovedFile(ChangeInfo change) {
for (EditorPartPresenter editorPartPresenter : editorAgent.getOpenedEditors()) {
String editorPath = editorPartPresenter.getEditorInput().getFile().getLocation().toString();
if (editorPath.equals(change.getOldPath())) {
deletedFilesController.add(change.getOldPath());
return;
}
}
}
private boolean isFileRemoved(String filePath, List<ChangeInfo> changes) {
for (ChangeInfo changeInfo : changes) {
if (!isNullOrEmpty(changeInfo.getOldPath()) && changeInfo.getOldPath().equals(filePath)) {
return true;
}
}
return false;
}
private void setActiveEditor() {
PartPresenter activePart = editorMultiPartStack.getActivePart();
if (activePart != null) {
workspaceAgent.setActivePart(activePart);
}
}
}