Skip to content

Content of file TreeContentProvider.java

/********************************************************************************
 * Copyright (c) 2011 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 2.0
 * which accompanies this distribution, and is available at
 * https://www.eclipse.org/legal/epl-2.0/
 *
 * SPDX-License-Identifier: EPL-2.0
 *
 * Contributors:
 * Eike Stepper - initial API and implementation
 ********************************************************************************/
package org.eclipse.emf.ecp.internal.ui.model;

import java.util.HashMap;
import java.util.Map;
import java.util.WeakHashMap;

import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecp.core.ECPProject;
import org.eclipse.emf.ecp.core.util.ECPUtil;
import org.eclipse.emf.ecp.internal.core.util.ChildrenListImpl;
import org.eclipse.emf.ecp.internal.ui.Activator;
import org.eclipse.emf.ecp.internal.ui.Messages;
import org.eclipse.emf.ecp.spi.core.InternalProvider;
import org.eclipse.emf.ecp.spi.core.util.InternalChildrenList;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.swt.widgets.Control;
import org.eclipse.swt.widgets.Display;

/**
 * @author Eike Stepper
 * @param <INPUT> The type of input (root of the tree)
 */
public abstract class TreeContentProvider<INPUT> extends StructuredContentProvider<INPUT> implements
	ITreeContentProvider {
	private static final Object[] NO_CHILDREN = new Object[0];

	private final Map<Object, Object> parentsCache = new WeakHashMap<Object, Object>();

	private final Map<Object, InternalChildrenList> slowLists = new HashMap<Object, InternalChildrenList>();

	@Override
	public TreeViewer getViewer() {
		return (TreeViewer) super.getViewer();
	}

	/** {@inheritDoc} */
	@Override
	public final Object[] getElements(Object parent) {
		return getChildren(parent);
	}

	/** {@inheritDoc} */
	@Override
	public final boolean hasChildren(Object parent) {
		if (parent instanceof SyntheticElement || ECPUtil.isDisposed(parent) || ECPUtil.isClosed(parent)) {
			return false;
		}

		final InternalChildrenList childrenList = getChildrenList(parent);
		synchronized (childrenList) {
			if (!childrenList.isComplete()) {
				return true;
			}

			return childrenList.hasChildren();
		}
	}

	/** {@inheritDoc} */
	@Override
	public final Object[] getChildren(Object parent) {
		if (parent instanceof SyntheticElement || ECPUtil.isDisposed(parent) || ECPUtil.isClosed(parent)) {
			return NO_CHILDREN;
		}

		Object[] result;
		boolean complete;

		final InternalChildrenList childrenList = getChildrenList(parent);
		synchronized (childrenList) {
			result = childrenList.getChildren();
			complete = childrenList.isComplete();
		}

		for (int i = 0; i < result.length; i++) {
			final Object child = result[i];
			parentsCache.put(child, parent);
		}

		if (!complete) {
			final Object[] withPending = new Object[result.length + 1];
			System.arraycopy(result, 0, withPending, 0, result.length);
			withPending[result.length] = new SlowElement(parent);
			result = withPending;
		}

		return result;
	}

	/** {@inheritDoc} */
	@Override
	public final Object getParent(Object child) {
		if (child instanceof SyntheticElement) {
			return ((SyntheticElement) child).getParent();
		}

		Object result = parentsCache.get(child);
		if (result == null && EObject.class.isInstance(child)) {
			final EObject childEObject = (EObject) child;
			result = childEObject.eContainer();
			if (result != null && parentsCache.containsKey(result)) {
				return result;

			}
		}
		return result;
	}

	public final void refreshViewer(final boolean isStructuralChange, final Object... objects) {
		if (objects.length == 0) {
			return;
		}

		final TreeViewer viewer = getViewer();
		final Control control = viewer.getControl();
		if (!control.isDisposed()) {
			final Display display = control.getDisplay();
			final ECPProject ecpProject = ECPUtil.getECPProjectManager()
				.getProject(objects[0]);
			boolean isThreadSafe = true;
			if (ecpProject != null) {
				final InternalProvider provider = (InternalProvider) ecpProject.getProvider();
				isThreadSafe = provider.isThreadSafe();
			}
			if (display.getSyncThread() != Thread.currentThread()) {
				final Runnable refreshRunnable = createRefreshRunnable(isStructuralChange, viewer, objects);
				if (Boolean.getBoolean("enableDisplaySync") && !isThreadSafe) {
display.syncExec(refreshRunnable); } else { display.asyncExec(refreshRunnable); } } else { if (isStructuralChange) { refresh(viewer, objects); } else { update(viewer, objects); } } } } private Runnable createRefreshRunnable(final boolean isStructuralChange, final TreeViewer viewer, final Object... objects) { return new Runnable() { @Override public void run() { if (isStructuralChange) { refresh(viewer, objects); } else { update(viewer, objects); } } }; } protected boolean isSlow(Object parent) { return false; } protected InternalChildrenList getChildrenList(Object parent) { InternalChildrenList childrenList; if (isSlow(parent)) { SlowChildrenList newList = null; synchronized (slowLists) { childrenList = slowLists.get(parent); if (childrenList == null) { newList = new SlowChildrenList(parent); childrenList = newList; slowLists.put(parent, childrenList); } } if (newList != null) { newList.startThread(); } } else { childrenList = new ChildrenListImpl(parent); fillChildrenDetectError(parent, childrenList); } return childrenList; } protected void fillChildrenDetectError(Object parent, InternalChildrenList childrenList) { try { fillChildren(parent, childrenList); } catch (final Throwable t) { Activator.log(t); final ErrorElement errorElement = new ErrorElement(parent, t); childrenList.addChildWithoutRefresh(errorElement); } } protected abstract void fillChildren(Object parent, InternalChildrenList childrenList); public static void refresh(TreeViewer viewer, Object... objects) { if (!viewer.getControl().isDisposed()) { for (final Object object : objects) { viewer.refresh(object); } } } public static void update(TreeViewer viewer, Object... objects) { if (!viewer.getControl().isDisposed()) { for (final Object object : objects) { if (object != null) { viewer.update(object, null); } } } } /** * @author Eike Stepper */ public static class SyntheticElement { private final Object parent; public SyntheticElement(Object parent) { this.parent = parent; } public final Object getParent() { return parent; } } /** * @author Eike Stepper */ public static final class ErrorElement extends SyntheticElement { private final Throwable cause; public ErrorElement(Object parent, Throwable cause) { super(parent); this.cause = cause; } public Throwable getCause() { return cause; } @Override public String toString() { return Messages.TreeContentProvider_ErrorElement_Error; } } /** * @author Eike Stepper */ public static final class SlowElement extends SyntheticElement { public SlowElement(Object parent) { super(parent); } @Override public String toString() { return Messages.TreeContentProvider_SlowElement_Pending; } } /** * @author Eike Stepper */ private final class SlowChildrenList extends ChildrenListImpl implements Runnable { private static final long serialVersionUID = 1L; private boolean complete; SlowChildrenList(Object parent) { super(parent); } public void startThread() { final Thread thread = new Thread(this, "SlowChildrenList"); //$NON-NLS-1$ thread.setDaemon(true); thread.start(); } @Override public void run() { fillChildrenDetectError(getParent(), this); setComplete(); } @Override public boolean isSlow() { return true; } @Override public boolean isComplete() { return complete; } @Override public void setComplete() { if (!complete) { try { complete = true; childrenAdded(); } finally { synchronized (slowLists) { slowLists.remove(getParent()); } } } } @Override protected void childrenAdded() { final TreeViewer viewer = getViewer(); final Control control = viewer.getControl(); if (!control.isDisposed()) { final Display display = control.getDisplay(); // asyncExec() would lead to infinite recursion in setComplete() display.syncExec(new Runnable() { @Override public void run() { if (!control.isDisposed()) { refresh(viewer, getParent()); } } }); } } // private void dumpStack(String msg) // { // try // { // Object parent = getParent(); // throw new RuntimeException(msg + " for " + parent + " (" + System.identityHashCode(parent) + ")"); // } // catch (Exception ex) // { // ex.printStackTrace(); // } // } } }