/******************************************************************************* * Copyright (c) 2006-2013 The RCP Company 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: * The RCP Company - initial API and implementation *******************************************************************************/ package com.rcpcompany.uibindings.internal; import java.util.HashMap; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Map.Entry; import org.eclipse.core.databinding.observable.DisposeEvent; import org.eclipse.core.databinding.observable.IDisposeListener; import org.eclipse.core.databinding.observable.IObservable; import org.eclipse.core.databinding.observable.list.IObservableList; import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory; import org.eclipse.emf.ecore.EObject; import org.eclipse.jface.databinding.viewers.ObservableListTreeContentProvider; import org.eclipse.jface.databinding.viewers.TreeStructureAdvisor; import org.eclipse.jface.viewers.TreeViewer; import org.eclipse.swt.widgets.Tree; import com.rcpcompany.uibindings.IChildCreationSpecification; import com.rcpcompany.uibindings.IConstantTreeItem; import com.rcpcompany.uibindings.IDisposable; import com.rcpcompany.uibindings.IElementParentage; import com.rcpcompany.uibindings.IManager; import com.rcpcompany.uibindings.ITreeItemDescriptor; import com.rcpcompany.uibindings.ITreeItemRelation; import com.rcpcompany.uibindings.IViewerBinding; import com.rcpcompany.utils.basic.ClassUtils; import com.rcpcompany.utils.logging.LogUtils; /** * Tree factory for use in a {@link IViewerBinding viewer binding} when used with a {@link Tree}. * <p> * Note that this list also acts as a {@link TreeStructureAdvisor} - see * {@link ObservableListTreeContentProvider} for details. * <p> * This class operates with two types of parent objects: parents in the object model (called * <em>model parents</em>) and parents in the view model (called <em>view parents</em>). * * @author Tonny Madsen, The RCP Company */ public class ViewerBindingTreeFactory extends TreeStructureAdvisor implements IObservableFactory, IDisposable { /** * Shortcut to the manager... */ protected static final IManager MANAGER = IManager.Factory.getManager(); /** * The root element as specified for {@link TreeViewer#setInput(Object)}. */ public static final Object ROOT_ELEMENT = new Object() { @Override public String toString() { return "ROOT"; }; }; /** * Map with all results returned by this factory. * <p> * Maps view parents to the list with the children in the parent. * <p> * An entry is removed when the list is disposed, which is done automatically by * {@link ObservableListTreeContentProvider} when an entry is not longer needed. */ private final Map<EObject, ViewerBindingTreeFactoryList> myResults = new HashMap<EObject, ViewerBindingTreeFactoryList>(); /** * The root elements of the tree. */ private final IObservableList myRootList; /** * The ID of the tree. */ private final String myTreeID; /** * Returns the ID of the tree. * * @return the tree ID - never <code>null</code> */ public String getTreeID() { return myTreeID; } /** * Constructs and returns a new tree factory for the specified viewer. * * @param rootList the root element * @param treeID the ID of the tree */ public ViewerBindingTreeFactory(IObservableList rootList, String treeID) { myRootList = rootList; myTreeID = treeID != null ? treeID : ""; } @Override public IObservable createObservable(Object target) { if (target == null) return null; if (Activator.getDefault().TRACE_TREE) { LogUtils.debug(this, "object [" + ClassUtils.getLastClassName(target) + "]: " + target); //$NON-NLS-1$ //$NON-NLS-2$ } /* * The root items */ if (target == ROOT_ELEMENT) return myRootList; /* * Look for any cached results */ final IObservableList list = myResults.get(target); if (list != null) return list; final EObject etarget; final ITreeItemDescriptor descriptor; /* * Find the target and the descriptor for the target */ if (target instanceof IConstantTreeItem) { final IConstantTreeItem item = (IConstantTreeItem) target; etarget = item.getTarget(); descriptor = item.getDescriptor(); } else if (target instanceof EObject) { etarget = (EObject) target; descriptor = MANAGER.getTreeItem(etarget); } else { LogUtils.error(this, "Target is not an EObject, but an " + ClassUtils.getLastClassName(target) + ": " + target); //$NON-NLS-1$ return null; } /* * If we don't have a descriptor, then we don't have any children! */ if (descriptor == null) // if (Activator.getDefault().TRACE_TREE) { // LogUtils.debug(this, // "Cannot find the descriptor for " + ClassUtils.getLastClassName(target) + ": " + target); //$NON-NLS-1$ // } return null; final ViewerBindingTreeFactoryList l = new ViewerBindingTreeFactoryList(this); l.addChildren(etarget, descriptor); /* * If the result is a constant list, then we replace it with a better performing version. * * We do not replace with a constant list as this makes in impossible to find the possible * child objects. */ // if (l.isConstant()) { // result = Observables.staticObservableList(new ArrayList<Object>(l), l.getElementType()); // l.dispose(); // } if (Activator.getDefault().TRACE_TREE) { LogUtils.debug(this, "--> " + l); //$NON-NLS-1$ } l.addDisposeListener(myDisposeListener); myResults.put((EObject) target, l); return l; } @Override public void dispose() { /* * Dispose all lists - the dispose listener will take care of the rest... */ final ViewerBindingTreeFactoryList[] lists = myResults.values().toArray( new ViewerBindingTreeFactoryList[myResults.values().size()]); for (final ViewerBindingTreeFactoryList l : lists) { l.dispose(); } } /** * Dispose listener used to remove mappings when the lists are disposed... */ private final IDisposeListener myDisposeListener = new IDisposeListener() { @Override public void handleDispose(DisposeEvent event) { final ViewerBindingTreeFactoryList l = (ViewerBindingTreeFactoryList) event.getObservable(); for (final Iterator<Entry<EObject, ViewerBindingTreeFactoryList>> i = myResults.entrySet().iterator(); i .hasNext();) { if (i.next().getValue() == l) { i.remove(); return; } } } }; /** * Returns the view parent object from {@link #myResults} that contains the specified child * element. * * @param element the element to find * @return the parent or <code>null</code> if not found */ private EObject findParent(Object element) { for (final Entry<EObject, ViewerBindingTreeFactoryList> e : myResults.entrySet()) { if (e.getValue().contains(element)) return e.getKey(); } return null; } /** * Returns whether the specified parent is already known in myResults - with other words, * calling {@link #createObservable(Object)} will not result in a new object being created. * * @param parent the parent to test for * @return <code>true</code> if already found in myResults, <code>false</code> otherwise */ private boolean hasParentObservable(Object parent) { if (parent == null) return false; /* * The root items */ if (parent == ROOT_ELEMENT) return true; /* * Look for any cached results */ final IObservableList list = myResults.get(parent); return list != null; } @Override public Boolean hasChildren(Object element) { final IObservableList list = (IObservableList) createObservable(element); if (list == null) return false; if (list instanceof ViewerBindingTreeFactoryList) { if (((ViewerBindingTreeFactoryList) list).isConstant()) return !list.isEmpty(); } return null; } @Override public Object getParent(Object child) { /* * The root elements from the tree */ if (child == myRootList) return null; if (child == ROOT_ELEMENT) return null; /* * Look at the parents in my previous results */ final Object parent = findParent(child); if (parent != null) return parent; final EObject echild; final ITreeItemDescriptor childDescriptor; /* * Find the target and the descriptor for the child */ if (child instanceof IConstantTreeItem) { final IConstantTreeItem item = (IConstantTreeItem) child; echild = item.getTarget(); childDescriptor = item.getDescriptor(); } else if (child instanceof EObject) { echild = (EObject) child; childDescriptor = MANAGER.getTreeItem(echild); } else { LogUtils.error(this, "Child is not an EObject, but an " + ClassUtils.getLastClassName(child) + ": " + child); //$NON-NLS-1$ return null; } /* * Look at all tree items to find a parent that * * - either have a direct child that is the same as */ // final ITreeItemDescriptor parentDesc = childDescriptor.getPrimaryParent(); // EObject parent = null; // if (parentDesc != null) { // parent = findParent(echild, childDescriptor, parentDesc, null); // if (parent != null) return parent; // // LogUtils.error(parentDesc, "Parent Descriptor is not a parent of " + echild); // } // // for (final ITreeItemRelation parentRel : childDescriptor.getParentRelations()) { // parent = findParent(echild, childDescriptor, parentRel.getParent(), parentRel); // if (parent != null) return parent; // } return null; } /** * Tests whether the specified parent descriptor can be an immediate parent of the specified * child. * * @param echild the child object * @param childDescriptor the child descriptor * @param parentDesc the parent descriptor to test * @param parentRelation possible parent relation to test * @return the parent object if found; otherwise <code>null</code> */ private EObject findParent(EObject echild, ITreeItemDescriptor childDescriptor, ITreeItemDescriptor parentDesc, ITreeItemRelation parentRelation) { for (final ITreeItemRelation relation : parentDesc.getChildRelations()) { } return null; } /** * Returns the model parentage for the element in this tree factory. * * @param element the element in question * @return an object that describes the model parentage or <code>null</code> if the parentage is * not known */ public IElementParentage getElementParentage(final EObject element) { /* * Find the parent (if any) for the list with the element... */ final EObject parent = findParent(element); if (parent == null) return null; final ViewerBindingTreeFactoryList list = myResults.get(parent); return list.getElementParentage(element); } /** * Returns a list of the possible objects that can be created at the specified parent as * sub-elements. * * @param parent the view element that should be the parent of the child (never * <code>null</code>) * @param sibling the wanted sibling or <code>null</code> * @return a list of possible children */ public List<IChildCreationSpecification> getPossibleChildObjects(EObject parent, EObject sibling) { final ViewerBindingTreeFactoryList list = myResults.get(parent); if (list == null) return null; return list.getPossibleChildObjects(sibling); } }