/*******************************************************************************
* 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.ArrayList;
import java.util.Collections;
import java.util.List;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.core.databinding.observable.IObserving;
import org.eclipse.core.databinding.observable.Observables;
import org.eclipse.core.databinding.observable.list.IListChangeListener;
import org.eclipse.core.databinding.observable.list.IObservableList;
import org.eclipse.core.databinding.observable.list.ListChangeEvent;
import org.eclipse.core.databinding.observable.list.ListDiffVisitor;
import org.eclipse.core.databinding.observable.list.ObservableList;
import org.eclipse.core.databinding.observable.masterdetail.IObservableFactory;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.WritableValue;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
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.IUIBindingsFactory;
import com.rcpcompany.uibindings.IUIBindingsPackage;
import com.rcpcompany.uibindings.IViewerBinding;
import com.rcpcompany.uibindings.UIBindingsEMFObservables;
import com.rcpcompany.uibindings.observables.EListKeyedElementObservableValue;
import com.rcpcompany.uibindings.utils.IBindingSpec;
import com.rcpcompany.uibindings.utils.IBindingSpec.SpecContext;
import com.rcpcompany.utils.logging.LogUtils;
/**
* Observable list special-made for {@link ViewerBindingTreeFactory}.
*
* @author Tonny Madsen, The RCP Company
*/
public class ViewerBindingTreeFactoryList extends ObservableList {
/**
* Shortcut to the manager...
*/
protected static final IManager THE_MANAGER = IManager.Factory.getManager();
/**
* The tree factory that created this list.
*/
private final ViewerBindingTreeFactory myFactory;
/**
* Constructs and returns a new list.
*
* @param factory the tree factory that created this list
*/
public ViewerBindingTreeFactoryList(ViewerBindingTreeFactory factory) {
super(Collections.EMPTY_LIST, EObject.class);
myFactory = factory;
if (Activator.getDefault().TRACE_TREE) {
addListChangeListener(new IListChangeListener() {
@Override
public void handleListChange(ListChangeEvent event) {
event.diff.accept(new ListDiffVisitor() {
@Override
public void handleRemove(int index, Object element) {
LogUtils.debug(ViewerBindingTreeFactoryList.this, "removed[" + index + "]: " + element);
}
@Override
public void handleAdd(int index, Object element) {
LogUtils.debug(ViewerBindingTreeFactoryList.this, "added[" + index + "]: " + element);
}
});
}
});
}
}
@Override
public synchronized void dispose() {
for (final IElement be : myElements) {
be.dispose();
}
super.dispose();
}
/**
* Adds the children of a new target element to the list.
*
* @param target the target
* @param descriptor the descriptor used for the target
*/
public void addChildren(EObject target, ITreeItemDescriptor descriptor) {
new RelationElement(target, descriptor);
}
/**
* Adds the specific target element to the list.
*
* @param target the target
*/
public void addDirect(EObject target) {
new DirectElement(target);
}
/**
* Returns whether the content of this list with the current children is constant.
* <p>
* A constant list can be optimized to a constant {@link IObservableList} by the caller.
*
* @return <code>true</code> if constant
*/
public boolean isConstant() {
for (final IElement be : myElements) {
if (!be.isConstant()) return false;
}
return true;
}
/**
* Listener used to recalculate the list when any of the added elements change.
*/
private final IChangeListener myRelationChangeListener = new IChangeListener() {
@Override
public void handleChange(ChangeEvent event) {
recalculateList();
}
};
/**
* The base elements of this list.
*/
private final List<IElement> myElements = new ArrayList<IElement>();
/**
* The basic interface for an element in the base list.
* <p>
* Each of the elements can add to the list of results
*/
protected interface IElement extends IDisposable {
/**
* Returns the target (parent) for this element.
*
* @return the target
*/
EObject getTarget();
/**
* Add of the generated elements for this element to the specified list.
*
* @param list the list to add the elements to
*/
void addToList(List<EObject> list);
/**
* Returns whether the content of this element is constant.
*
* @return <code>true</code> if constant
*/
boolean isConstant();
/**
* @see ViewerBindingTreeFactoryList#getElementParentage(EObject)
*/
IElementParentage getElementParentage(final EObject element);
/**
* @param sibling TODO
* @see ViewerBindingTreeFactoryList#getPossibleChildObjects()
*/
void getPossibleChildObjects(List<IChildCreationSpecification> l, EObject sibling);
}
/**
* A direct element.
*/
protected class DirectElement implements IElement {
private final EObject myTarget;
@Override
public EObject getTarget() {
return myTarget;
}
/**
* Constructs and returns a new direct element.
*
* @param target the target
*/
protected DirectElement(EObject target) {
myTarget = target;
myElements.add(this);
recalculateList();
}
@Override
public void dispose() {
}
@Override
public void addToList(List<EObject> list) {
list.add(myTarget);
}
@Override
public boolean isConstant() {
return true;
}
@Override
public IElementParentage getElementParentage(EObject element) {
return null;
}
@Override
public void getPossibleChildObjects(List<IChildCreationSpecification> l, EObject sibling) {
}
}
/**
* Element that will add child elements based on the relations from the element itself.
*/
protected class RelationElement implements IElement {
private final EObject myTarget;
private final ITreeItemDescriptor myDescriptor;
private List<Relation> myRelations = null;
@Override
public EObject getTarget() {
return myTarget;
}
/**
* Constructs and returns new base element.
*
* @param target the target
* @param descriptor the descriptor used for the target
*/
protected RelationElement(EObject target, ITreeItemDescriptor descriptor) {
myTarget = target;
myDescriptor = descriptor;
myElements.add(this);
/*
* List of observables - both IOV and IOL - that will be the children of this item.
* Ironed out later
*
* We get them in the correct sequence as the list is already sorted by priority
*/
for (final ITreeItemRelation rel : descriptor.getChildRelations()) {
final String treeID = myFactory.getTreeID();
if (treeID.length() > 0) {
if (!rel.getTreeIDs().contains(treeID)) {
continue;
}
} else {
/*
* There are two cases if there are no tree id:
*
* - if the relation has no associated tree ids, then use the relation
*
* - if the relation has a tree id list, then only use the relation iff "" is a
* member of the list
*
* [The first part of this condition is for optimizations as most relation will
* probably not have an associated tree id list...]
*/
if (rel.eIsSet(IUIBindingsPackage.Literals.TREE_ITEM_RELATION__TREE_IDS)
&& rel.getTreeIDs().size() > 0 && !rel.getTreeIDs().contains("")) {
continue;
}
}
if (rel.getFactory() != null) {
final IObservableFactory factory = rel.getFactory().getObject();
if (factory == null) {
continue;
}
try {
final IObservable res = factory.createObservable(target);
if (res == null) {
LogUtils.error(rel.getFactory().getConfigurationElement(), "Factory returns null. Ignored");
continue;
}
addRelation(rel, res, null, null);
} catch (final Exception ex) {
LogUtils.error(rel.getFactory().getConfigurationElement(), ex);
}
} else if (rel.getFeatureName() != null) {
final String sfName = rel.getFeatureName();
final List<IBindingSpec> specList = IBindingSpec.Factory.parseSingleSpec(target.eClass(), sfName,
SpecContext.OBSERVABLE);
if (specList == null) {
continue;
}
IObservableValue parentValue = WritableValue.withValueType(target.eClass());
parentValue.setValue(target);
IObservable nextValue = null;
EStructuralFeature lastFeature = null;
for (final IBindingSpec s : specList) {
if (nextValue != null) {
parentValue = (IObservableValue) nextValue;
}
final EStructuralFeature feature = s.getFeature();
lastFeature = s.getResultFeature();
switch (s.getType()) {
default:
break;
case FEATURE:
if (feature.isMany()) {
if (!s.isLast()) {
LogUtils.error(feature, "Only last feature can be to-many");
}
nextValue = UIBindingsEMFObservables.observeDetailList(parentValue, feature);
} else {
nextValue = UIBindingsEMFObservables.observeDetailValue(parentValue, feature);
}
break;
case KEY_VALUE:
if (feature.isMany()) {
LogUtils.error(feature, "Key/Value spec must be to-one");
} else {
nextValue = new EListKeyedElementObservableValue<EObject>(null, parentValue,
(EReference) feature, s.getKeyFeature(), s.getKeyValue(), s.getValueFeature());
}
break;
}
}
/*
* At this point we have
*
* parentValue - non-null - the parent of the last node
*
* nextValue - non-null - the current values for this relation
*/
addRelation(rel, nextValue, null, lastFeature);
} else {
final IConstantTreeItem item = IUIBindingsFactory.eINSTANCE.createConstantTreeItem();
item.setDescriptor(rel.getDescriptor());
item.setTarget(target);
addRelation(rel, Observables.constantObservableValue(item), rel.getDescriptor(), null);
}
}
recalculateList();
}
private void addRelation(final ITreeItemRelation rel, IObservable observable,
ITreeItemDescriptor childDescriptor, EStructuralFeature feature) {
if (Activator.getDefault().TRACE_TREE) {
observable.addChangeListener(new IChangeListener() {
@Override
public void handleChange(ChangeEvent event) {
LogUtils.debug(ViewerBindingTreeFactoryList.this, "rel changed: " + rel);
}
});
}
if (myRelations == null) {
myRelations = new ArrayList<Relation>();
}
myRelations.add(new Relation(rel, observable, childDescriptor, feature));
}
@Override
public void dispose() {
if (myRelations == null) return;
for (final Relation rel : myRelations) {
rel.dispose();
}
}
/**
* Information about the relations elements that ends up on the list.
* <p>
* Each base element corresponds to one
*/
protected class Relation implements IDisposable {
private final ITreeItemRelation myRelation;
private final IObservable myObservable;
private final ITreeItemDescriptor myChildDescriptor;
private final EStructuralFeature myFeature;
/**
* Constructs and returns new base element.
*
* @param relation
* @param observable
* @param childDescriptor TODO
* @param feature TODO
*/
private Relation(ITreeItemRelation relation, IObservable observable, ITreeItemDescriptor childDescriptor,
EStructuralFeature feature) {
myRelation = relation;
myObservable = observable;
myChildDescriptor = childDescriptor;
myFeature = feature;
myObservable.addChangeListener(myRelationChangeListener);
}
@Override
public void dispose() {
myObservable.removeChangeListener(myRelationChangeListener);
}
public void addToList(List<EObject> newList) {
if (myObservable instanceof IObservableValue) {
final IObservableValue ov = (IObservableValue) myObservable;
final EObject value = (EObject) ov.getValue();
if (value == null) return;
/*
* Test if we are to hide a child because it does not have any children itself.
*/
if (value instanceof IConstantTreeItem && myChildDescriptor != null
&& myChildDescriptor.isEmptyFolderHidden()) {
final IObservableList childList = (IObservableList) myFactory.createObservable(value);
if (childList == null || childList.isEmpty()) return;
}
newList.add(value);
return;
}
if (myObservable instanceof IObservableList) {
final IObservableList ol = (IObservableList) myObservable;
newList.addAll(ol);
return;
}
}
/**
* Returns whether the content of this specific relation is constant.
*
* @return <code>true</code> if constant
*/
public boolean isConstant() {
if (myObservable instanceof IObservableValue) {
if (((IObservableValue) myObservable).getValue() instanceof IConstantTreeItem
&& myChildDescriptor != null && !myChildDescriptor.isEmptyFolderHidden()) return true;
return false;
}
if (myObservable instanceof IObservableList) return false;
return false;
}
/**
* @see ViewerBindingTreeFactoryList#getElementParentage(EObject)
*/
public IElementParentage getElementParentage(final EObject element) {
Object type = null;
EObject parent = null;
if (myObservable instanceof IObservableValue) {
final IObservableValue ov = (IObservableValue) myObservable;
final EObject value = (EObject) ov.getValue();
if (value != element) return null;
if (ov instanceof IObserving) {
parent = (EObject) ((IObserving) ov).getObserved();
}
type = ov.getValueType();
}
if (myObservable instanceof IObservableList) {
final IObservableList ol = (IObservableList) myObservable;
if (!ol.contains(element)) return null;
if (ol instanceof IObserving) {
parent = (EObject) ((IObserving) ol).getObserved();
}
type = ol.getElementType();
}
if (!(type instanceof EReference)) return null;
final EReference ref = (EReference) type;
final EObject target = parent;
return new IElementParentage() {
@Override
public EReference getReference() {
return ref;
}
@Override
public EObject getParent() {
return target;
}
@Override
public EObject getElement() {
return element;
}
};
}
public void getPossibleChildObjects(List<IChildCreationSpecification> l, EObject sibling) {
if (myRelation.getFactory() != null) {
/*
* Cannot handle processors...
*/
} else if (myRelation.getFeatureName() != null) {
final EReference ref = (EReference) myFeature;
int index = -1;
if (myFeature.isMany()) {
/*
* If we seek a sibling, then look for it in the existing list
*/
if (sibling != null) {
final IObservableList ol = (IObservableList) myObservable;
index = ol.indexOf(sibling);
if (index == -1) return;
}
} else {
/*
* to-one reference: can only be added if not already present
*/
final IObservableValue value = (IObservableValue) myObservable;
if (value.getValue() != null) return;
}
final EObject target = (EObject) ((IObserving) myObservable).getObserved();
if (target == null) return;
IViewerBinding.Factory.addToChildCreationSpecification(l, target, ref, ref.getEReferenceType(),
index);
} else {
final IConstantTreeItem item = IUIBindingsFactory.eINSTANCE.createConstantTreeItem();
item.setDescriptor(myRelation.getDescriptor());
item.setTarget(getTarget());
final ViewerBindingTreeFactoryList list = (ViewerBindingTreeFactoryList) myFactory
.createObservable(item);
l.addAll(list.getPossibleChildObjects(sibling));
}
}
}
@Override
public void addToList(List<EObject> newList) {
if (myRelations == null) return;
for (final Relation rel : myRelations) {
rel.addToList(newList);
}
}
@Override
public boolean isConstant() {
if (myRelations == null) return true;
for (final Relation rel : myRelations) {
if (!rel.isConstant()) return false;
}
return true;
}
@Override
public IElementParentage getElementParentage(EObject element) {
if (myRelations == null) return null;
for (final Relation rel : myRelations) {
final IElementParentage p = rel.getElementParentage(element);
if (p != null) return p;
}
return null;
}
@Override
public void getPossibleChildObjects(List<IChildCreationSpecification> l, EObject sibling) {
if (myRelations == null) return;
for (final Relation rel : myRelations) {
rel.getPossibleChildObjects(l, sibling);
}
}
}
/**
* Updates the list based on the added elements.
*/
public void recalculateList() {
final List<EObject> newList = new ArrayList<EObject>();
for (final IElement be : myElements) {
be.addToList(newList);
}
updateWrappedList(newList);
}
@Override
public String toString() {
return super.toString() + "@" + hashCode();
}
/**
* Returns the parentage for the element in this list.
*
* @param element the element in question
* @return an object that describes the parentage or <code>null</code> if the parentage is not
* known
*/
public IElementParentage getElementParentage(final EObject element) {
for (final IElement be : myElements) {
final IElementParentage p = be.getElementParentage(element);
if (p != null) return p;
}
return null;
}
/**
* Returns a list of the possible objects that can be created as sub-elements of this list.
*
* @param sibling the wanted sibling or <code>null</code>
* @return a list of possible children
*/
public List<IChildCreationSpecification> getPossibleChildObjects(EObject sibling) {
final List<IChildCreationSpecification> l = new ArrayList<IChildCreationSpecification>();
for (final IElement be : myElements) {
be.getPossibleChildObjects(l, sibling);
}
return l;
}
}