/*******************************************************************************
* 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.part.explorer.project;
import com.google.common.base.Optional;
import com.google.gwt.core.client.Scheduler;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.user.client.rpc.AsyncCallback;
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.Function;
import org.eclipse.che.api.promises.client.FunctionException;
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.api.promises.client.PromiseProvider;
import org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper.RequestCall;
import org.eclipse.che.ide.DelayedTask;
import org.eclipse.che.ide.api.data.tree.Node;
import org.eclipse.che.ide.resource.Path;
import org.eclipse.che.ide.resources.reveal.RevealResourceEvent;
import org.eclipse.che.ide.resources.reveal.RevealResourceEvent.RevealResourceHandler;
import org.eclipse.che.ide.resources.tree.ResourceNode;
import org.eclipse.che.ide.ui.smartTree.Tree;
import org.eclipse.che.ide.ui.smartTree.event.PostLoadEvent;
import org.eclipse.che.ide.ui.smartTree.event.PostLoadEvent.PostLoadHandler;
import java.util.List;
import static com.google.common.base.Optional.absent;
import static com.google.common.base.Optional.of;
import static java.util.Arrays.copyOf;
import static org.eclipse.che.api.promises.client.callback.AsyncPromiseHelper.createFromAsyncRequest;
/**
* Search node handler, perform searching specified node in the tree by storable value.
* For example if user passes "/project/path/to/file" then this node handler will check
* opened root nodes and if it contains project node with path "/project" then it will
* search children by path "path/to/file".
*
* @author Vlad Zhukovskiy
* @see RevealResourceEvent
* @since 4.4.0
*/
@Singleton
public class TreeResourceRevealer {
private Promise<Void> queue;
private Tree tree;
private Node[] toSelect = null;
private DelayedTask selectTask = new DelayedTask() {
@Override
public void onExecute() {
if (toSelect != null) {
final Node[] copy = copyOf(toSelect, toSelect.length);
if (copy.length == 1) {
tree.getSelectionModel().select(copy[0], false);
tree.scrollIntoView(copy[0]);
}
toSelect = null;
}
}
};
@Inject
public TreeResourceRevealer(ProjectExplorerView projectExplorer, EventBus eventBus, PromiseProvider promises) {
this.tree = projectExplorer.getTree();
queue = promises.resolve(null);
eventBus.addHandler(RevealResourceEvent.getType(), new RevealResourceHandler() {
@Override
public void onRevealResource(final RevealResourceEvent event) {
queue.thenPromise(new Function<Void, Promise<Void>>() {
@Override
public Promise<Void> apply(Void ignored) throws FunctionException {
return reveal(event.getLocation()).catchError(new Function<PromiseError, Void>() {
@Override
public Void apply(PromiseError arg) throws FunctionException {
return null;
}
});
}
});
}
});
}
/**
* Search node in the project explorer tree by storable path.
*
* @param path
* path to node
* @return promise object with found node or promise error if node wasn't found
*/
public Promise<Node> reveal(final Path path) {
return reveal(path, true);
}
/**
* Search node in the project explorer tree by storable path.
*
* @param path
* path to node
* @param select
* select node after reveal
* @return promise object with found node or promise error if node wasn't found
*/
public Promise<Node> reveal(final Path path, final boolean select) {
return queue.thenPromise(new Function<Void, Promise<Node>>() {
@Override
public Promise<Node> apply(Void ignored) throws FunctionException {
return createFromAsyncRequest(new RequestCall<Node>() {
@Override
public void makeCall(AsyncCallback<Node> callback) {
reveal(path, select, callback);
}
});
}
});
}
protected void reveal(final Path path, final boolean select, final AsyncCallback<Node> callback) {
if (path == null) {
callback.onFailure(new IllegalArgumentException("Invalid search path"));
}
Scheduler.get().scheduleFixedDelay(new Scheduler.RepeatingCommand() {
@Override
public boolean execute() {
if (tree.getNodeLoader().isBusy()) {
return true;
}
final Optional<ResourceNode> optRoot = getRootResourceNode(path);
if (!optRoot.isPresent()) {
callback.onFailure(new IllegalStateException());
return false;
}
final ResourceNode root = optRoot.get();
if (root.getData().getLocation().equals(path)) {
callback.onSuccess(root);
return false;
}
expandToPath(root, path, select).then(new Operation<ResourceNode>() {
@Override
public void apply(ResourceNode node) throws OperationException {
callback.onSuccess(node);
}
}).catchError(new Operation<PromiseError>() {
@Override
public void apply(PromiseError arg) throws OperationException {
callback.onFailure(arg.getCause());
}
});
return false;
}
}, 500);
}
private Promise<ResourceNode> expandToPath(final ResourceNode root, final Path path, final boolean select) {
return createFromAsyncRequest(new RequestCall<ResourceNode>() {
@Override
public void makeCall(final AsyncCallback<ResourceNode> callback) {
expand(root, path, select, callback);
}
});
}
protected void expand(final ResourceNode parent, final Path segment, final boolean select, final AsyncCallback<ResourceNode> callback) {
if (parent.getData().getLocation().equals(segment)) {
if (select) {
if (toSelect == null) {
toSelect = new Node[]{parent};
} else {
final int index = toSelect.length;
toSelect = copyOf(toSelect, index + 1);
toSelect[index] = parent;
}
selectTask.delay(200);
}
callback.onSuccess(parent);
return;
}
final HandlerRegistration[] handler = new HandlerRegistration[1];
handler[0] = tree.getNodeLoader().addPostLoadHandler(new PostLoadHandler() {
@Override
public void onPostLoad(PostLoadEvent event) {
if (!event.getRequestedNode().equals(parent)) {
return;
}
if (handler[0] != null) {
handler[0].removeHandler();
}
final List<Node> children = tree.getNodeStorage().getChildren(event.getRequestedNode());
for (Node child : children) {
if (child instanceof ResourceNode && ((ResourceNode)child).getData().getLocation().isPrefixOf(segment)) {
expand((ResourceNode)child, segment, select, callback);
return;
}
}
callback.onFailure(new IllegalStateException("Not found"));
}
});
tree.getNodeLoader().loadChildren(parent);
}
private Optional<ResourceNode> getRootResourceNode(Path path) {
for (Node root : tree.getRootNodes()) {
if (!(root instanceof ResourceNode)) {
continue;
}
final Path rootPath = ((ResourceNode)root).getData().getLocation();
if (!rootPath.isPrefixOf(path)) {
continue;
}
return of((ResourceNode)root);
}
return absent();
}
}