/*****************************************************************************
* Copyright (c) 2010 CEA LIST.
*
* 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:
* Camille Letavernier (CEA LIST) camille.letavernier@cea.fr - Initial API and implementation
*****************************************************************************/
package org.eclipse.papyrus.views.properties.modelelement;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import org.eclipse.core.databinding.observable.ChangeEvent;
import org.eclipse.core.databinding.observable.IChangeListener;
import org.eclipse.core.databinding.observable.IObservable;
import org.eclipse.jface.viewers.ILabelProvider;
import org.eclipse.jface.viewers.IStructuredSelection;
import org.eclipse.papyrus.infra.widgets.creation.ReferenceValueFactory;
import org.eclipse.papyrus.infra.widgets.providers.EmptyContentProvider;
import org.eclipse.papyrus.infra.widgets.providers.IStaticContentProvider;
import org.eclipse.papyrus.views.properties.Activator;
import org.eclipse.papyrus.views.properties.contexts.View;
/**
* A DataSource is an object encapsulating one or more {@link ModelElement}s.
* It contains methods to resolve property paths, and forward the methods to
* the right ModelElement.
*
* For example, a UML class stereotyped with the SysML::Blocks::Block will have
* two ModelElements : one for UML, and one for the Block stereotype.
*
* It will be able to resolve paths such as UML:Class:name or
* SysML:Blocks:Block:isEncapsulated
*
* The methods such as isUnique, isEditable or getContentProvider will be
* delegated to the resolved ModelElement, with a truncated property path.
*
* For example, a call to DataSource#isEditable("UML:Class:name") will be
* forwarded to UMLModelElement#isEditable("name")
*
* @author Camille Letavernier
*/
public class DataSource implements IChangeListener {
private Set<IChangeListener> changeListeners = new HashSet<IChangeListener>();
private View view;
private IStructuredSelection selection;
private Map<String, ModelElement> elements = new HashMap<String, ModelElement>();
/**
* Constructs a new DataSource from the given view and selection
*
* @param view
* @param selection
*
* @see DataSourceFactory#createDataSourceFromSelection(IStructuredSelection, View)
*/
protected DataSource(View view, IStructuredSelection selection) {
this.view = view;
this.selection = selection;
}
/**
* Return the instance of ModelElement associated to the given path
*
* @param propertyPath
* The propertyPath to lookup
* @return
* The ModelElement associated to the given propertyPath
*/
public ModelElement getModelElement(String propertyPath) {
//ConfigurationManager.instance.getProperty(propertyPath)
String key = propertyPath.substring(0, propertyPath.lastIndexOf(":")); //$NON-NLS-1$
if(!elements.containsKey(key)) { //Try to resolve the modelElements on-the-fly
ModelElement element = DataSourceFactory.instance.getModelElementFromPropertyPath(this, propertyPath);
if(element == null) {
Activator.log.warn("Unable to find a ModelElement for " + propertyPath + ". Elements : " + elements); //$NON-NLS-1$ //$NON-NLS-2$
}
elements.put(key, element);
}
return elements.get(key);
}
private String getLocalPropertyPath(String propertyPath) {
return propertyPath.substring(propertyPath.lastIndexOf(":") + 1); //$NON-NLS-1$
}
/**
* Returns an IObservable corresponding to the given property path
* The observable may be either an IObservableValue or an IObservableList
* The call to this method is delegated to the corresponding ModelElement
* The IObservable objects returned by this method may be shared by
* many instances, which means they should not be disposed directly.
* They will be disposed when this DataSource is disposed.
*
* @param propertyPath
* The property path for which we want to retrieve an ObservableValue
* @return
* The IObservable corresponding to the given propertyPath
*/
public IObservable getObservable(String propertyPath) {
String localPropertyPath = getLocalPropertyPath(propertyPath);
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return null;
}
IObservable observable = element.getObservable(localPropertyPath);
if(observable != null) {
observable.addChangeListener(this);
}
return observable;
}
@Override
public String toString() {
return "[DataSource] " + super.toString(); //$NON-NLS-1$
}
/**
* Returns an IStaticContentProvider corresponding to the given property path
* The call to this method is delegated to the corresponding ModelElement
*
* @param propertyPath
* The property path for which we want to retrieve a ContentProvider
* @return
* The IStaticContentProvider corresponding to the given propertyPath
*/
public IStaticContentProvider getContentProvider(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return EmptyContentProvider.instance;
}
String localPropertyPath = getLocalPropertyPath(propertyPath);
return element.getContentProvider(localPropertyPath);
}
/**
* Returns an ILabelProvider corresponding to the given property path
* The call to this method is delegated to the corresponding ModelElement
*
* @param propertyPath
* The property path for which we want to retrieve an ILabelProvider
* @return
* The ILabelProvider corresponding to the given propertyPath
*/
public ILabelProvider getLabelProvider(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return null;
}
String localPropertyPath = getLocalPropertyPath(propertyPath);
return element.getLabelProvider(localPropertyPath);
}
/**
* Adds a change listener to this DataSource. The listener will be notified
* each time a change occurs on one of the IObservable produced by this DataSource
*
* @see DataSource#getObservable(String)
* @param listener
* The Change listener
*/
public void addChangeListener(IChangeListener listener) {
changeListeners.add(listener);
}
/**
* Removes a change listener from this DataSource.
*
* @param listener
* The listener to remove
* @see DataSource#addChangeListener(IChangeListener)
*/
public void removeChangeListener(IChangeListener listener) {
changeListeners.remove(listener);
}
public void handleChange(ChangeEvent event) {
//The set of listeners may change during the update.
Set<IChangeListener> listeners = new HashSet<IChangeListener>(changeListeners);
for(IChangeListener listener : listeners) {
listener.handleChange(event);
}
}
/**
* @return The view associated to this DataSource
*/
public View getView() {
return view;
}
/**
* @return the selection associated to this DataSource
*/
public IStructuredSelection getSelection() {
return selection;
}
/**
* @param propertyPath
* @return
* true if the property represented by this propertyPath is ordered
*/
public boolean isOrdered(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return false;
}
return element.isOrdered(getLocalPropertyPath(propertyPath));
}
/**
* @param propertyPath
* @return
* true if the property represented by this propertyPath is unique
*/
public boolean isUnique(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return false;
}
return element.isUnique(getLocalPropertyPath(propertyPath));
}
/**
* @param propertyPath
* @return
* true if the property represented by this propertyPath is mandatory
*/
public boolean isMandatory(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return false;
}
return element.isMandatory(getLocalPropertyPath(propertyPath));
}
/**
* @param propertyPath
* @return
* true if the property represented by this propertyPath is editable
*/
public boolean isEditable(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return false;
}
return element.isEditable(getLocalPropertyPath(propertyPath));
}
/**
* Returns true if the given property should be refresh each time a change
* occurs in the property view. May help when the IObservable doesn't
* catch some change events (For example, for some Ecore derived
* properties).
*
* @param propertyPath
* @return true if the refresh should be forced
*/
public boolean forceRefresh(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return false;
}
return element.forceRefresh(getLocalPropertyPath(propertyPath));
}
/**
* Return the value factory associated to the given path. May be null
*
* @param propertyPath
* The property path to lookup
* @return
* The factory used to edit and/or instantiate values for this property path
*/
public ReferenceValueFactory getValueFactory(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return null;
}
return element.getValueFactory(getLocalPropertyPath(propertyPath));
}
/**
* Return the default value for the given property path
*
* @param propertyPath
* @return
* The default value for the given property
*/
public Object getDefaultValue(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return null;
}
return element.getDefaultValue(getLocalPropertyPath(propertyPath));
}
/**
* Indicates if the widget should use the direct creation.
* The direct edition will disable the possibility to browse
* existing elements when the "add" button is pressed.
*
* This is essentially relevant for containment references : this method
* should return false if the widget should only allow creation of new
* elements.
*
* @param propertyPath
* @return
* True if the widget should use the direct edition option for the given property
*/
public boolean getDirectCreation(String propertyPath) {
ModelElement element = getModelElement(propertyPath);
if(element == null) {
return true;
}
return element.getDirectCreation(getLocalPropertyPath(propertyPath));
}
/**
* Disposes this data source.
* This will dispose all ModelElements and IObservable created by this DataSource
*/
public void dispose() {
for(ModelElement element : elements.values()) {
if(element != null) {
element.dispose();
}
}
elements.clear();
}
}