| /* |
| * 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); |
| } |
| } |
| } |