/******************************************************************************* * 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.observables; import java.util.ArrayList; import java.util.Collection; import org.eclipse.core.databinding.observable.IObserving; 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.WritableList; import org.eclipse.emf.common.notify.Adapter; import org.eclipse.emf.common.notify.Notification; import org.eclipse.emf.common.notify.impl.AdapterImpl; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import com.rcpcompany.uibindings.IManager; import com.rcpcompany.utils.logging.LogUtils; /** * An {@link IObservableList observable list} that given an observed list and a mapper object, will * return a new list with the elements from the list mapped with the mapper. * <p> * Alternatively, the list can also take an EMF based observed list and an a * {@link EStructuralFeature structural feature} will provide a new list with the values of the * objects of the original list with the specific feature. * * @author Tonny Madsen, The RCP Company */ public class EMFListAttributeList extends WritableList implements IObserving { private final IObservableList myObjectList; private final IObservableListMapper myMapper; /** * The value used to replace <code>null</code> values in the resulting list. * <p> * Thus, if the mapper returns <code>null</code>, this value is used instead (if set). */ private final Object myNullReplacementValue; /** * Returns the value used to replace <code>null</code> values in the resulting list. * * @return the current replacement value */ public Object getNullReplacementValue() { return myNullReplacementValue; } /** * Sets the special label to be used for the <code>null</code> value. * * @param nullLabel the <code>null</code> label */ public void setNullLabel(String nullLabel) { super.add(nullLabel); } /** * Constructs and returns a new list for the specified list and mapper object. * * @param objectList the observed list * @param mapper the mapper object * @param type the element type of the resulting list */ public EMFListAttributeList(IObservableList objectList, IObservableListMapper mapper, Object type, Object nullReplacementValue) { super(new ArrayList<Object>(), type); myObjectList = objectList; myMapper = mapper; myNullReplacementValue = nullReplacementValue; init(); } /** * Constructs and returns a new list for the specified list and mapper object. * * @param objectList the observed list * @param mapper the mapper object * @param type the element type of the resulting list */ public EMFListAttributeList(IObservableList objectList, IObservableListMapper mapper, Object type) { this(objectList, mapper, type, null); } /** * Constructs and returns a new list for the specified EMF list and feature. * * @param objectList the EMF list to monitor * @param structuralfeature the structural feature to retrieve */ public EMFListAttributeList(IObservableList objectList, final EStructuralFeature structuralfeature) { this(objectList, new IObservableListMapper() { @Override public Object map(Object value) { return ((EObject) value).eGet(structuralfeature); } }, structuralfeature); } private final Adapter myFeatureMonitor = new AdapterImpl() { @Override public void notifyChanged(final Notification msg) { /* * Cannot use toString() as this calls getterCalled().... * * LogUtils.error(EMFListAttributeList.this , "List disposed: " + * EMFListAttributeList.this); */ if (isDisposed()) return; if (msg.isTouch()) return; if (msg.getEventType() != Notification.SET) return; myObjectList.getRealm().exec(new Runnable() { @Override public void run() { final int index = myObjectList.indexOf(msg.getNotifier()); if (index == -1) return; final Object oldValue = get(index); Object newValue; try { newValue = myMapper.map(msg.getNotifier()); } catch (final Exception ex) { newValue = null; LogUtils.error(myMapper, ex); } if (newValue == null ? oldValue == null : newValue.equals(oldValue)) return; EMFListAttributeList.super.set(index, newValue); } }); }; }; private final IListChangeListener myListMonitor = new IListChangeListener() { private final ListDiffVisitor visitor = new ListDiffVisitor() { @Override public void handleRemove(int index, Object element) { if (element != null && hasListeners()) { ((EObject) element).eAdapters().remove(myFeatureMonitor); } EMFListAttributeList.super.remove(index); } @Override public void handleAdd(int index, Object element) { EMFListAttributeList.super.add(index, mapValue(element)); if (element != null && hasListeners()) { ((EObject) element).eAdapters().add(myFeatureMonitor); } } }; @Override public void handleListChange(ListChangeEvent event) { event.diff.accept(visitor); } }; private void init() { IManager.Factory.getManager().startMonitorObservableDispose(myObjectList, this); for (final Object o : myObjectList) { super.add(mapValue(o)); } } /** * Maps a single value with the mapper. * <p> * If * * @param obj the object to map * @return the mapped value */ private Object mapValue(final Object obj) { Object v; try { v = myMapper.map(obj); } catch (final Exception ex) { LogUtils.error(myMapper, ex); v = null; } if (v == null) { v = myNullReplacementValue; } return v; } @Override public synchronized void dispose() { IManager.Factory.getManager().stopMonitorObservableDispose(myObjectList); if (hasListeners()) { lastListenerRemoved(); } super.dispose(); } @Override protected void firstListenerAdded() { super.firstListenerAdded(); myObjectList.addListChangeListener(myListMonitor); for (final Object o : myObjectList) { final EObject obj = (EObject) o; if (obj != null) { obj.eAdapters().add(myFeatureMonitor); } } } @Override protected void lastListenerRemoved() { for (final Object o : myObjectList) { final EObject obj = (EObject) o; if (obj != null) { obj.eAdapters().remove(myFeatureMonitor); } } myObjectList.removeListChangeListener(myListMonitor); super.lastListenerRemoved(); } @Override public Object getObserved() { return myObjectList; } @Override public void add(int index, Object element) { throw new UnsupportedOperationException(); } @Override public boolean add(Object element) { throw new UnsupportedOperationException(); } @Override public boolean addAll(Collection c) { throw new UnsupportedOperationException(); } @Override public boolean addAll(int index, Collection c) { throw new UnsupportedOperationException(); } @Override public Object remove(int index) { throw new UnsupportedOperationException(); } @Override public boolean remove(Object o) { throw new UnsupportedOperationException(); } @Override public boolean removeAll(Collection c) { throw new UnsupportedOperationException(); } @Override public Object set(int index, Object element) { throw new UnsupportedOperationException(); } @Override public void clear() { throw new UnsupportedOperationException(); } @Override public Object move(int oldIndex, int newIndex) { throw new UnsupportedOperationException(); } @Override public boolean retainAll(Collection c) { throw new UnsupportedOperationException(); } }