blob: a77439697013ff2dc69d7463e497b7b09d46558f [file] [log] [blame]
/*
* Copyright (c) 2020 Eike Stepper (Loehne, 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.emf.cdo.internal.ui;
import org.eclipse.emf.cdo.CDOElement;
import org.eclipse.emf.cdo.CDOObject;
import org.eclipse.emf.cdo.common.id.CDOID;
import org.eclipse.emf.cdo.common.revision.CDOList;
import org.eclipse.emf.cdo.common.revision.CDORevision;
import org.eclipse.emf.cdo.common.revision.CDORevisionManager;
import org.eclipse.emf.cdo.eresource.CDOResource;
import org.eclipse.emf.cdo.internal.ui.bundle.OM;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevision;
import org.eclipse.emf.cdo.spi.common.revision.InternalCDORevisionCache;
import org.eclipse.emf.cdo.util.CDOUtil;
import org.eclipse.emf.cdo.view.CDOView;
import org.eclipse.net4j.util.lifecycle.LifecycleException;
import org.eclipse.net4j.util.lifecycle.LifecycleUtil;
import org.eclipse.net4j.util.ui.UIUtil;
import org.eclipse.net4j.util.ui.views.ItemProvider;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.InternalEObject;
import org.eclipse.emf.ecore.resource.Resource;
import org.eclipse.emf.ecore.resource.ResourceSet;
import org.eclipse.emf.edit.provider.ITreeItemContentProvider;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.emf.spi.cdo.InternalCDOObject;
import org.eclipse.emf.spi.cdo.InternalCDOView;
import org.eclipse.core.runtime.IProgressMonitor;
import org.eclipse.core.runtime.IStatus;
import org.eclipse.core.runtime.Status;
import org.eclipse.core.runtime.jobs.Job;
import org.eclipse.jface.viewers.ITreeContentProvider;
import org.eclipse.jface.viewers.StructuredViewer;
import org.eclipse.jface.viewers.TreeViewer;
import org.eclipse.jface.viewers.Viewer;
import org.eclipse.swt.widgets.TreeItem;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.concurrent.ConcurrentHashMap;
/**
* @author Eike Stepper
*/
public abstract class CDOContentProvider<CONTEXT> implements ITreeContentProvider
{
private static final Set<Object> LOADING_OBJECTS = new HashSet<>();
private static final Method GET_CHILDREN_FEATURES_METHOD = getMethod(ItemProviderAdapter.class, "getChildrenFeatures", Object.class);
private static final Method FIND_ITEM_METHOD = getMethod(StructuredViewer.class, "findItem", Object.class);
private final Map<Object, Object[]> childrenCache = new ConcurrentHashMap<>();
private TreeViewer viewer;
private Object input;
private boolean hideObjects;
public CDOContentProvider()
{
}
public final TreeViewer getViewer()
{
return viewer;
}
public final boolean isHideObjects()
{
return hideObjects;
}
public final void setHideObjects(boolean hideObjects)
{
boolean oldValue = this.hideObjects;
if (hideObjects != oldValue)
{
this.hideObjects = hideObjects;
if (viewer != null)
{
UIUtil.asyncExec(viewer.getControl().getDisplay(), () -> UIUtil.refreshViewer(viewer));
}
}
}
@Override
public void inputChanged(Viewer newViewer, Object oldInput, Object newInput)
{
TreeViewer newTreeViewer = null;
if (newViewer instanceof TreeViewer)
{
newTreeViewer = (TreeViewer)newViewer;
}
if (newTreeViewer != viewer)
{
if (viewer != null)
{
unhookViewer(viewer);
}
viewer = newTreeViewer;
if (viewer != null)
{
hookViewer(viewer);
}
}
input = newInput;
}
public Object getInput()
{
return input;
}
@Override
public Object[] getElements(Object object)
{
return getChildren(object);
}
@Override
public boolean hasChildren(Object object)
{
try
{
if (object instanceof ViewerUtil.Pending)
{
return false;
}
if (hideObjects && object instanceof CDOResource)
{
CDOResource resource = (CDOResource)object;
if (!resource.isRoot())
{
return false;
}
}
if (isContext(object))
{
@SuppressWarnings("unchecked")
CONTEXT context = (CONTEXT)object;
switch (getContextState(context))
{
case Closed:
return false;
case Opening:
// This must be the ViewerUtil.Pending element.
return true;
case Open:
object = getRootObject(context);
break;
}
}
if (object instanceof CDOElement)
{
CDOElement element = (CDOElement)object;
return element.hasChildren();
}
if (GET_CHILDREN_FEATURES_METHOD != null && object instanceof EObject)
{
EObject eObject = (EObject)object;
InternalCDOObject cdoObject = getCDOObject(eObject);
if (cdoObject != null)
{
InternalCDORevision revision = cdoObject.cdoRevision(false);
if (revision != null)
{
try
{
ITreeItemContentProvider provider = (ITreeItemContentProvider)adapt(object, ITreeItemContentProvider.class);
if (provider instanceof ItemProviderAdapter)
{
return hasChildren(cdoObject, revision, (ItemProviderAdapter)provider);
}
}
catch (Exception ex)
{
//$FALL-THROUGH$
}
}
}
}
ITreeContentProvider contentProvider = getContentProvider(object);
if (contentProvider != null)
{
return contentProvider.hasChildren(object);
}
}
catch (LifecycleException ex)
{
//$FALL-THROUGH$
}
catch (RuntimeException ex)
{
if (LifecycleUtil.isActive(object))
{
throw ex;
}
//$FALL-THROUGH$
}
return false;
}
@Override
public Object[] getChildren(Object object)
{
try
{
if (object instanceof ViewerUtil.Pending)
{
return ViewerUtil.NO_CHILDREN;
}
if (hideObjects && object instanceof CDOResource)
{
CDOResource resource = (CDOResource)object;
if (!resource.isRoot())
{
return ViewerUtil.NO_CHILDREN;
}
}
if (object instanceof CDOElement)
{
CDOElement element = (CDOElement)object;
return element.getChildren();
}
Object originalObject = object;
Object[] children = childrenCache.remove(originalObject);
if (children != null)
{
return children;
}
CONTEXT openingContext = null;
if (isContext(object))
{
@SuppressWarnings("unchecked")
CONTEXT context = (CONTEXT)object;
switch (getContextState(context))
{
case Closed:
return ViewerUtil.NO_CHILDREN;
case Opening:
openingContext = context;
break;
case Open:
object = getRootObject(context);
break;
}
}
Object finalObject = object;
CONTEXT finalOpeningContext = openingContext;
ITreeContentProvider contentProvider = getContentProvider(finalObject);
if (contentProvider == null)
{
return ItemProvider.NO_ELEMENTS;
}
List<CDORevision> loadedRevisions = new ArrayList<>();
List<CDOID> missingIDs = new ArrayList<>();
if (finalOpeningContext == null)
{
children = determineChildRevisions(object, loadedRevisions, missingIDs);
if (children != null)
{
return modifyChildren(object, children);
}
}
boolean firstLoad;
synchronized (LOADING_OBJECTS)
{
firstLoad = LOADING_OBJECTS.add(originalObject);
}
if (firstLoad || finalOpeningContext == null)
{
Job job = new Job("Load " + finalObject)
{
@Override
protected IStatus run(IProgressMonitor monitor)
{
CDOView view = null;
try
{
if (finalOpeningContext != null)
{
openContext(finalOpeningContext);
determineChildRevisions(finalObject, loadedRevisions, missingIDs);
}
if (!missingIDs.isEmpty())
{
CDOObject cdoObject = getCDOObject((EObject)finalObject);
view = cdoObject.cdoView();
CDORevisionManager revisionManager = view.getSession().getRevisionManager();
List<CDORevision> revisions = revisionManager.getRevisions(missingIDs, view, CDORevision.UNCHUNKED, CDORevision.DEPTH_NONE, true);
loadedRevisions.addAll(revisions);
}
Object[] children = contentProvider.getChildren(finalObject);
// Adjust possible legacy adapters.
for (int i = 0; i < children.length; i++)
{
Object child = children[i];
if (child instanceof InternalCDOObject)
{
InternalCDOObject cdoObject = (InternalCDOObject)child;
InternalEObject instance = cdoObject.cdoInternalInstance();
if (instance != cdoObject)
{
children[i] = instance;
}
}
}
children = modifyChildren(finalObject, children);
childrenCache.put(originalObject, children);
}
catch (Exception ex)
{
childrenCache.remove(originalObject);
if (finalOpeningContext != null)
{
closeContext(finalOpeningContext);
}
if (view == null)
{
CDOObject cdoObject = getCDOObject((EObject)finalObject);
view = cdoObject.cdoView();
}
if (view == null || !view.isClosed())
{
OM.LOG.error(ex);
}
// final Control control = viewer.getControl();
// if (!control.isDisposed())
// {
// UIUtil.getDisplay().asyncExec(new Runnable()
// {
// @Override
// public void run()
// {
// try
// {
// if (!control.isDisposed())
// {
// Shell shell = control.getShell();
// String title = (finalOpeningContext != null ? "Open" : "Load") + " Error";
// MessageDialog.openError(shell, title, ex.getMessage());
// }
// }
// catch (Exception ex)
// {
// OM.LOG.error(ex);
// }
// }
// });
// }
}
RunnableViewerRefresh viewerRefresh = getViewerRefresh();
viewerRefresh.addNotification(originalObject, true, true, () -> {
synchronized (LOADING_OBJECTS)
{
LOADING_OBJECTS.remove(originalObject);
}
});
return Status.OK_STATUS;
}
};
job.schedule();
}
if (FIND_ITEM_METHOD != null)
{
try
{
Object widget = FIND_ITEM_METHOD.invoke(viewer, originalObject);
if (widget instanceof TreeItem)
{
TreeItem item = (TreeItem)widget;
TreeItem[] childItems = item.getItems();
int childCount = childItems.length;
if (childCount != 0)
{
List<Object> result = new ArrayList<>();
for (int i = 0; i < childCount; i++)
{
TreeItem childItem = childItems[i];
Object child = childItem.getData();
if (child != null)
{
result.add(child);
}
}
int size = result.size();
if (size != 0)
{
return result.toArray(new Object[size]);
}
}
}
}
catch (Exception ex)
{
//$FALL-THROUGH$
}
}
String text = "Loading...";
if (finalOpeningContext != null)
{
text = "Opening...";
}
return new Object[] { new ViewerUtil.Pending(originalObject, text) };
}
catch (LifecycleException ex)
{
//$FALL-THROUGH$
}
catch (RuntimeException ex)
{
if (LifecycleUtil.isActive(object))
{
throw ex;
}
//$FALL-THROUGH$
}
return ItemProvider.NO_ELEMENTS;
}
@Override
public Object getParent(Object object)
{
try
{
if (object instanceof ViewerUtil.Pending)
{
return ((ViewerUtil.Pending)object).getParent();
}
if (object instanceof CDOElement)
{
CDOElement element = (CDOElement)object;
return element.getParent();
}
if (object instanceof EObject)
{
EObject eObject = CDOUtil.getEObject((EObject)object);
CDOElement element = CDOElement.getFor(eObject);
if (element != null)
{
return element;
}
ITreeContentProvider contentProvider = getContentProvider(object);
if (contentProvider != null)
{
return contentProvider.getParent(object);
}
}
}
catch (LifecycleException ex)
{
//$FALL-THROUGH$
}
catch (RuntimeException ex)
{
if (LifecycleUtil.isActive(object))
{
throw ex;
}
//$FALL-THROUGH$
}
return null;
}
protected void hookViewer(TreeViewer viewer)
{
}
protected void unhookViewer(TreeViewer viewer)
{
}
protected abstract Object adapt(Object target, Object type);
protected abstract Object[] modifyChildren(Object parent, Object[] children);
protected abstract ITreeContentProvider getContentProvider(Object object);
protected abstract RunnableViewerRefresh getViewerRefresh();
protected abstract boolean isContext(Object object);
protected abstract ContextState getContextState(CONTEXT context);
protected abstract void openContext(CONTEXT context);
protected abstract void closeContext(CONTEXT context);
protected abstract Object getRootObject(CONTEXT context);
private Object[] determineChildRevisions(Object object, List<CDORevision> loadedRevisions, List<CDOID> missingIDs)
{
if (object instanceof EObject)
{
if (GET_CHILDREN_FEATURES_METHOD != null)
{
EObject eObject = (EObject)object;
InternalCDOObject cdoObject = getCDOObject(eObject);
if (cdoObject != null)
{
InternalCDORevision revision = cdoObject.cdoRevision(false);
if (revision != null)
{
try
{
ITreeItemContentProvider provider = (ITreeItemContentProvider)adapt(object, ITreeItemContentProvider.class);
if (provider instanceof ItemProviderAdapter)
{
determineChildRevisions(cdoObject, revision, (ItemProviderAdapter)provider, loadedRevisions, missingIDs);
if (missingIDs.isEmpty())
{
// All revisions are cached. Just return the objects without server round-trips.
ITreeContentProvider contentProvider = getContentProvider(object);
if (contentProvider != null)
{
return contentProvider.getChildren(object);
}
}
}
}
catch (Exception ex)
{
//$FALL-THROUGH$
}
}
}
}
}
else if (object instanceof ResourceSet)
{
EList<Resource> resources = ((ResourceSet)object).getResources();
return resources.toArray(new Resource[resources.size()]);
}
return null;
}
protected static boolean isObjectLoading(Object... objects)
{
synchronized (LOADING_OBJECTS)
{
for (Object object : objects)
{
if (LOADING_OBJECTS.contains(object))
{
return true;
}
}
return false;
}
}
private static InternalCDOObject getCDOObject(EObject eObject)
{
return (InternalCDOObject)CDOUtil.getCDOObject(eObject, false);
}
private static boolean hasChildren(InternalCDOObject cdoObject, InternalCDORevision revision, ItemProviderAdapter provider) throws Exception
{
for (EStructuralFeature feature : getChildrenFeatures(cdoObject, provider))
{
if (feature.isMany())
{
if (!revision.isEmpty(feature))
{
return true;
}
}
else if (revision.getValue(feature) != null)
{
return true;
}
}
return false;
}
private static void determineChildRevisions(InternalCDOObject cdoObject, InternalCDORevision revision, ItemProviderAdapter provider,
List<CDORevision> loadedRevisions, List<CDOID> missingIDs) throws Exception
{
InternalCDOView view = cdoObject.cdoView();
InternalCDORevisionCache revisionCache = view.getSession().getRevisionManager().getCache();
for (EStructuralFeature feature : getChildrenFeatures(cdoObject, provider))
{
if (feature.isMany())
{
CDOList list = revision.getListOrNull(feature);
if (list != null)
{
for (Object object : list)
{
determineChildRevision(loadedRevisions, missingIDs, view, revisionCache, object);
}
}
}
else
{
Object value = revision.getValue(feature);
determineChildRevision(loadedRevisions, missingIDs, view, revisionCache, value);
}
}
}
private static void determineChildRevision(List<CDORevision> loadedRevisions, List<CDOID> missingIDs, InternalCDOView view, InternalCDORevisionCache cache,
Object object)
{
if (object instanceof CDOID)
{
CDOID id = (CDOID)object;
CDORevision childRevision = cache.getRevision(id, view);
if (childRevision != null)
{
loadedRevisions.add(childRevision);
}
else
{
missingIDs.add(id);
}
}
}
@SuppressWarnings("unchecked")
private static Collection<? extends EStructuralFeature> getChildrenFeatures(InternalCDOObject cdoObject, ItemProviderAdapter provider) throws Exception
{
return (Collection<? extends EStructuralFeature>)GET_CHILDREN_FEATURES_METHOD.invoke(provider, cdoObject);
}
private static Method getMethod(Class<?> c, String methodName, Class<?>... parameterTypes)
{
try
{
Method method = c.getDeclaredMethod(methodName, parameterTypes);
method.setAccessible(true);
return method;
}
catch (Throwable ex)
{
return null;
}
}
/**
* @author Eike Stepper
*/
public static enum ContextState
{
Opening, Open, Closed
}
}