blob: 7b1d7da9ec1928982a46efaa3440f24ace00bab2 [file] [log] [blame]
/*
* Copyright (c) 2007-2013, 2015 Eike Stepper (Berlin, Germany) 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
*
* Contributors:
* Eike Stepper - initial API and implementation
*/
package org.eclipse.net4j.util.ui.views;
import org.eclipse.net4j.internal.util.bundle.OM;
import org.eclipse.net4j.ui.shared.SharedIcons;
import org.eclipse.net4j.util.container.ContainerEventAdapter;
import org.eclipse.net4j.util.container.IContainer;
import org.eclipse.net4j.util.container.IContainerEvent;
import org.eclipse.net4j.util.container.ISlow;
import org.eclipse.net4j.util.container.SetContainer;
import org.eclipse.net4j.util.event.EventUtil;
import org.eclipse.net4j.util.event.IEvent;
import org.eclipse.net4j.util.event.IListener;
import org.eclipse.net4j.util.lifecycle.LifecycleState;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.ui.UIUtil;
import org.eclipse.jface.action.Action;
import org.eclipse.jface.action.IMenuManager;
import org.eclipse.jface.viewers.ITreeSelection;
import org.eclipse.jface.viewers.TreePath;
import org.eclipse.swt.SWT;
import org.eclipse.swt.graphics.Color;
import org.eclipse.swt.graphics.Font;
import org.eclipse.swt.graphics.Image;
import org.eclipse.ui.PartInitException;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
/**
* @author Eike Stepper
*/
/**
* @author Eike Stepper
*/
public class ContainerItemProvider<CONTAINER extends IContainer<Object>> extends ItemProvider<CONTAINER>
{
/**
* @since 3.5
*/
public static final Color PENDING_COLOR = UIUtil.getDisplay().getSystemColor(SWT.COLOR_DARK_GRAY);
/**
* @since 3.5
*/
public static final Image PENDING_IMAGE = SharedIcons.getImage(SharedIcons.OBJ_PENDING);
/**
* @since 3.5
*/
public static final Image ERROR_IMAGE = SharedIcons.getImage(SharedIcons.OBJ_ERROR);
private Map<Object, Node> nodes = new HashMap<Object, Node>();
private Node root;
private IElementFilter rootElementFilter;
public ContainerItemProvider()
{
}
public ContainerItemProvider(IElementFilter rootElementFilter)
{
this.rootElementFilter = rootElementFilter;
}
public IElementFilter getRootElementFilter()
{
return rootElementFilter;
}
@Override
public boolean hasChildren(Object element)
{
try
{
Node node = getNode(element);
if (node != null)
{
return node.hasChildren();
}
}
catch (RuntimeException ex)
{
//$FALL-THROUGH$
}
return false;
}
public Object[] getChildren(Object element)
{
try
{
Node node = getNode(element);
if (node != null)
{
List<Node> children = node.getChildren();
for (Iterator<Node> it = children.iterator(); it.hasNext();)
{
Node child = it.next();
if (child.isDisposed())
{
it.remove();
}
else
{
Object childElement = child.getElement();
LifecycleState lifecycleState = LifecycleUtil.getLifecycleState(childElement);
if (lifecycleState == LifecycleState.INACTIVE || lifecycleState == LifecycleState.DEACTIVATING)
{
handleInactiveElement(it, child);
}
}
}
Object[] result = new Object[children.size()];
for (int i = 0; i < result.length; i++)
{
result[i] = children.get(i).getElement();
}
return result;
}
}
catch (RuntimeException ex)
{
//$FALL-THROUGH$
}
return NO_ELEMENTS;
}
public Object getParent(Object element)
{
try
{
Node node = getNode(element);
if (node != null)
{
Node parentNode = node.getParent();
return parentNode == null ? null : parentNode.getElement();
}
}
catch (RuntimeException ex)
{
//$FALL-THROUGH$
}
return null;
}
/**
* @since 3.4
*/
public void clearNodesCache()
{
disposeRoot();
CONTAINER input = getInput();
initRoot(input);
}
private void initRoot(CONTAINER input)
{
root = createNode(null, input);
if (root != null)
{
addNode(input, root);
}
}
private void disposeRoot()
{
root.dispose(); // Also disposes of all children
root = null;
nodes.clear();
}
@Override
protected void connectInput(CONTAINER input)
{
initRoot(input);
}
@Override
protected void disconnectInput(CONTAINER input)
{
disposeRoot();
}
/**
* @since 2.0
*/
protected void handleInactiveElement(Iterator<Node> it, Node child)
{
it.remove();
child.dispose();
}
protected void elementAdded(Object element, Object parent)
{
}
protected void elementRemoved(Object element, Object parent)
{
}
/**
* @since 3.3
*/
protected void handleElementEvent(IEvent event)
{
}
/**
* @since 3.5
*/
protected Object[] getContainerChildren(AbstractContainerNode containerNode, IContainer<?> container)
{
return container.getElements();
}
protected Node getRoot()
{
return root;
}
protected Map<Object, Node> getNodes()
{
return nodes;
}
protected Node getNode(Object element)
{
if (element == getInput())
{
return root;
}
return nodes.get(element);
}
protected Node createNode(Node parent, Object element)
{
if (element instanceof IContainer<?>)
{
return createContaineNode(parent, element);
}
return createLeafNode(parent, element);
}
protected LeafNode createLeafNode(Node parent, Object element)
{
return new LeafNode(parent, element);
}
@SuppressWarnings("unchecked")
protected ContainerNode createContaineNode(Node parent, Object element)
{
return new ContainerNode(parent, (IContainer<Object>)element);
}
protected void addNode(Object element, Node node)
{
nodes.put(element, node);
}
protected Node removeNode(Object element)
{
return nodes.remove(element);
}
protected boolean filterRootElement(Object element)
{
if (rootElementFilter != null)
{
return rootElementFilter.filter(element);
}
return true;
}
/**
* @since 3.1
*/
protected void executeRunnable(Runnable runnable)
{
Thread thread = new Thread(runnable);
thread.setDaemon(true);
thread.start();
}
/**
* @since 3.5
*/
protected SlowElement createSlowElement(IContainer<?> container)
{
@SuppressWarnings("unchecked")
IContainer<Object> objectContainer = (IContainer<Object>)container;
String text = getSlowText(objectContainer);
return new SlowElement(objectContainer, text);
}
/**
* @since 3.5
*/
protected boolean isComputeChildrenEagerly()
{
return true;
}
/**
* @since 3.1
*/
protected boolean isSlow(IContainer<Object> container)
{
return container instanceof ISlow;
}
/**
* @since 3.1
*/
protected String getSlowText(IContainer<Object> container)
{
return "Pending...";
}
/**
* @since 3.1
*/
protected String getErrorText(IContainer<Object> container)
{
return "Error";
}
/**
* @since 3.3
*/
@Override
public void fillContextMenu(IMenuManager manager, ITreeSelection selection)
{
super.fillContextMenu(manager, selection);
if (selection.size() == 1)
{
Object element = selection.getFirstElement();
if (element instanceof ContainerItemProvider.ErrorElement)
{
manager.add(new Action("Open Error Log")
{
@Override
public void run()
{
try
{
UIUtil.getActiveWorkbenchPage().showView(UIUtil.ERROR_LOG_ID);
}
catch (PartInitException ex)
{
OM.LOG.error(ex);
}
}
});
}
}
}
@Override
public Font getFont(Object obj)
{
if (obj instanceof ContainerItemProvider.SlowElement)
{
return getItalicFont();
}
return super.getFont(obj);
}
@Override
public Color getForeground(Object obj)
{
if (obj instanceof ContainerItemProvider.SlowElement)
{
return PENDING_COLOR;
}
return super.getForeground(obj);
}
@Override
public Image getImage(Object obj)
{
if (obj instanceof ContainerItemProvider.SlowElement)
{
return PENDING_IMAGE;
}
if (obj instanceof ContainerItemProvider.ErrorElement)
{
return ERROR_IMAGE;
}
return super.getImage(obj);
}
/**
* @since 3.5
*/
public static IContainer<Object> createSlowInput(final String text)
{
return new SetContainer<Object>(Object.class)
{
{
addElement(new ContainerItemProvider.SlowElement(this, text));
}
};
}
/**
* @author Eike Stepper
* @noextend This interface is not intended to be extended by clients.
* @noimplement This interface is not intended to be implemented by clients.
*/
public interface Node
{
public boolean isDisposed();
public void dispose();
/**
* @since 3.5
*/
public void disposeChildren();
public Object getElement();
public Node getParent();
/**
* @since 3.5
*/
public boolean hasChildren();
public List<Node> getChildren();
public TreePath getTreePath();
}
/**
* @author Eike Stepper
*/
public abstract class AbstractNode implements Node
{
private Node parent;
private boolean disposed;
public AbstractNode(Node parent)
{
this.parent = parent;
}
public boolean isDisposed()
{
return disposed;
}
public void dispose()
{
if (!disposed)
{
removeNode(getElement());
parent = null;
disposed = true;
}
}
/**
* @since 3.5
*/
public void disposeChildren()
{
}
@Override
public String toString()
{
return MessageFormat.format("{0}[{1}]", getClass().getSimpleName(), getElement()); //$NON-NLS-1$
}
public final Node getParent()
{
checkNotDisposed();
return parent;
}
public TreePath getTreePath()
{
TreePath parentPath = parent == null ? TreePath.EMPTY : parent.getTreePath();
return parentPath.createChildPath(getElement());
}
/**
* @since 3.5
*/
public boolean hasChildren()
{
return false;
}
protected void checkNotDisposed()
{
if (disposed)
{
throw new IllegalStateException("Node is already disposed of"); //$NON-NLS-1$
}
}
protected Node addChild(Collection<Node> children, Object element)
{
if (nodes.containsKey(element))
{
return null;
}
if (this != root || filterRootElement(element))
{
Node node = createNode(this, element);
if (node != null)
{
addNode(element, node);
children.add(node);
return node;
}
}
return null;
}
}
/**
* @author Eike Stepper
*/
public abstract class AbstractContainerNode extends AbstractNode
{
private List<Node> children;
protected IListener containerListener = new ContainerEventAdapter<Object>()
{
@Override
protected void notifyContainerEvent(IContainerEvent<Object> event)
{
super.notifyContainerEvent(event);
handleElementEvent(event);
}
@Override
protected void onAdded(IContainer<Object> container, Object element)
{
AbstractContainerNode.this.onAdded(container, element);
}
@Override
protected void onRemoved(IContainer<Object> container, Object element)
{
AbstractContainerNode.this.onRemoved(container, element);
}
@Override
protected void notifyOtherEvent(IEvent event)
{
updateLabels(event.getSource());
handleElementEvent(event);
}
};
public AbstractContainerNode(Node parent)
{
super(parent);
}
@Override
public void dispose()
{
if (!isDisposed())
{
disposeChildren();
containerListener = null;
super.dispose();
}
}
@Override
public void disposeChildren()
{
if (children != null)
{
for (Node child : children)
{
child.dispose();
}
children.clear();
children = null;
}
}
/**
* @since 3.4
*/
@Override
public boolean hasChildren()
{
checkNotDisposed();
final IContainer<Object> container = getContainer();
if (children == null && isSlow(container))
{
if (isComputeChildrenEagerly())
{
getChildren();
}
return true;
}
List<Node> children = getChildren();
return children != null && !children.isEmpty();
}
public final List<Node> getChildren()
{
checkNotDisposed();
if (children == null)
{
children = createChildren();
}
return children;
}
@SuppressWarnings("unchecked")
public IContainer<Object> getContainer()
{
return (IContainer<Object>)getElement();
}
protected List<Node> createChildren()
{
final List<Node> children = new ArrayList<Node>();
final IContainer<Object> container = getContainer();
if (isSlow(container))
{
final Node[] lazyNode = { null };
SlowElement slowElement = createSlowElement(container);
if (slowElement != null)
{
lazyNode[0] = addChild(children, slowElement);
}
Runnable runnable = new Runnable()
{
public void run()
{
try
{
fillChildren(children, container);
}
catch (Exception ex)
{
OM.LOG.error(ex);
addChild(children, new ErrorElement(container));
}
finally
{
if (lazyNode[0] != null)
{
children.remove(lazyNode[0]);
}
refreshElement(container, true);
}
}
};
executeRunnable(runnable);
}
else
{
fillChildren(children, container);
}
container.addListener(containerListener);
return children;
}
/**
* @since 3.1
*/
protected void fillChildren(List<Node> children, IContainer<Object> container)
{
Object[] elements = getContainerChildren(this, container);
for (int i = 0; i < elements.length; i++)
{
Object element = elements[i];
addChild(children, element);
}
}
protected void onAdded(IContainer<Object> container, Object element)
{
Node node = addChild(getChildren(), element);
if (node != null)
{
refreshElement(container, true);
revealElement(element);
elementAdded(element, container);
}
}
protected void onRemoved(IContainer<Object> container, Object element)
{
Node node = removeNode(element);
if (node != null)
{
getChildren().remove(node);
elementRemoved(element, container);
Object rootElement = root.getElement();
Object refreshElement = container == rootElement ? null : container;
refreshElement(refreshElement, true);
node.dispose();
}
}
}
/**
* @author Eike Stepper
*/
public class ContainerNode extends AbstractContainerNode
{
private IContainer<Object> container;
public ContainerNode(Node parent, IContainer<Object> container)
{
super(parent);
this.container = container;
if (container == null)
{
throw new IllegalArgumentException("container == null"); //$NON-NLS-1$
}
}
@Override
public void dispose()
{
if (!isDisposed())
{
container.removeListener(containerListener);
super.dispose();
container = null;
}
}
public Object getElement()
{
return container;
}
}
/**
* @author Eike Stepper
*/
public class LeafNode extends AbstractNode implements IListener
{
private Object element;
public LeafNode(Node parent, Object element)
{
super(parent);
this.element = element;
EventUtil.addListener(element, this);
}
@Override
public void dispose()
{
if (!isDisposed())
{
EventUtil.removeListener(element, this);
element = null;
super.dispose();
}
}
public Object getElement()
{
checkNotDisposed();
return element;
}
public List<Node> getChildren()
{
checkNotDisposed();
return Collections.emptyList();
}
public void notifyEvent(IEvent event)
{
updateLabels(event.getSource());
handleElementEvent(event);
}
}
/**
* @author Eike Stepper
* @since 3.5
*/
public static class SlowElement
{
private final IContainer<Object> container;
private final String text;
public SlowElement(IContainer<Object> container, String text)
{
this.container = container;
this.text = text;
}
public final IContainer<Object> getContainer()
{
return container;
}
public final String getText()
{
return text;
}
@Override
public String toString()
{
return text;
}
}
/**
* @author Eike Stepper
* @since 3.1
* @deprecated as of 3.5 use {@link SlowElement}.
*/
@Deprecated
public class LazyElement extends SlowElement
{
/**
* @since 3.5
*/
public LazyElement(IContainer<Object> container, String text)
{
super(container, text);
}
public LazyElement(IContainer<Object> container)
{
this(container, getSlowText(container));
}
}
/**
* @author Eike Stepper
* @since 3.1
*/
public class ErrorElement
{
private IContainer<Object> container;
public ErrorElement(IContainer<Object> container)
{
this.container = container;
}
public IContainer<Object> getContainer()
{
return container;
}
@Override
public String toString()
{
return getErrorText(container);
}
}
}