/*****************************************************************************
* 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.uml.tools.databinding;
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.value.AbstractObservableValue;
import org.eclipse.core.databinding.observable.value.IObservableValue;
import org.eclipse.core.databinding.observable.value.ValueDiff;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.gmf.runtime.common.core.command.ICommand;
import org.eclipse.gmf.runtime.emf.type.core.requests.SetRequest;
import org.eclipse.papyrus.commands.wrappers.GMFtoEMFCommandWrapper;
import org.eclipse.papyrus.infra.services.edit.service.ElementEditServiceUtils;
import org.eclipse.papyrus.infra.services.edit.service.IElementEditService;
import org.eclipse.papyrus.infra.tools.databinding.AggregatedObservable;
import org.eclipse.papyrus.uml.tools.Activator;
import org.eclipse.papyrus.uml.tools.helper.UMLDatabindingHelper;
import org.eclipse.uml2.uml.UMLPackage;
/**
* An ObservableValue for manipulating the UML Multiplicity property.
* Multiplicity is a simple, virtual property, aggregating both lowerBound and upperBound,
* and presenting them as an Enumeration with 4 values : 1, 0-1, 0-*, 1-*
*
* The values are edited with commands executed on the given editing domain.
* These commands will probably only work in a Papyrus context.
*
* @author Camille Letavernier
*/
public class MultiplicityObservableValue extends AbstractObservableValue implements IChangeListener, CommandBasedObservableValue, AggregatedObservable {
/**
* The 0..* multiplicity (Any)
*/
public static String ANY = "0..*"; //$NON-NLS-1$
/**
* The * multiplicity (Any)
* Equivalent to 0..*
*/
public static String STAR = "*"; //$NON-NLS-1$
/***
* The 1 multiplicity (One)
*/
public static String ONE = "1"; //$NON-NLS-1$
/**
* The 0..1 multiplicity (Optional)
*/
public static String OPTIONAL = "0..1"; //$NON-NLS-1$
/**
* The 1..* multiplicity (One or more)
*/
public static String ONE_OR_MORE = "1..*"; //$NON-NLS-1$
/**
* The multiplicity separator (..)
*/
public static String SEPARATOR = ".."; //$NON-NLS-1$
private IObservableValue lowerBound, upperBound, lowerValue, upperValue, lowerValueSpecification, upperValueSpecification;
private EStructuralFeature lowerFeature, upperFeature;
private EObject eObject;
private EditingDomain domain;
/**
* Constructor.
*
* @param eObject
* The EObject which the multiplicity is being edited
* @param domain
* The Editing Domain on which the commands will be executed
*/
public MultiplicityObservableValue(EObject eObject, EditingDomain domain) {
this.eObject = eObject;
this.domain = domain;
lowerFeature = UMLPackage.eINSTANCE.getMultiplicityElement_Lower();
upperFeature = UMLPackage.eINSTANCE.getMultiplicityElement_Upper();
EStructuralFeature lowerValueFeature, upperValueFeature, lowerValueSpecificationFeature, upperValueSpecificationFeature;
lowerValueFeature = UMLPackage.eINSTANCE.getMultiplicityElement_LowerValue();
upperValueFeature = UMLPackage.eINSTANCE.getMultiplicityElement_UpperValue();
lowerValueSpecificationFeature = UMLPackage.eINSTANCE.getLiteralInteger_Value();
upperValueSpecificationFeature = UMLPackage.eINSTANCE.getLiteralUnlimitedNatural_Value();
lowerBound = UMLDatabindingHelper.getObservableValue(eObject, lowerFeature, domain);
upperBound = UMLDatabindingHelper.getObservableValue(eObject, upperFeature, domain);
lowerValue = UMLDatabindingHelper.getObservableValue(eObject, lowerValueFeature, domain);
upperValue = UMLDatabindingHelper.getObservableValue(eObject, upperValueFeature, domain);
lowerValueSpecification = getValueSpecification(lowerValue, lowerValueSpecificationFeature, domain);
upperValueSpecification = getValueSpecification(upperValue, upperValueSpecificationFeature, domain);
lowerValue.addChangeListener(this);
upperValue.addChangeListener(this);
if(lowerValueSpecification != null) {
lowerValueSpecification.addChangeListener(this);
}
if(upperValueSpecification != null) {
upperValueSpecification.addChangeListener(this);
}
}
private IObservableValue getValueSpecification(IObservableValue source, EStructuralFeature specificationFeature, EditingDomain domain) {
if(source.getValue() == null) {
return null;
}
return UMLDatabindingHelper.getObservableValue((EObject)source.getValue(), specificationFeature, domain);
}
/**
* @see org.eclipse.core.databinding.observable.IChangeListener#handleChange(org.eclipse.core.databinding.observable.ChangeEvent)
*
* @param event
*/
public void handleChange(ChangeEvent event) {
boolean fireChange = false;
if(event.getSource() == lowerValue || event.getSource() == upperValue) {
fireChange = true;
lowerValueSpecification = getValueSpecification(lowerValue, UMLPackage.eINSTANCE.getLiteralInteger_Value(), domain);
upperValueSpecification = getValueSpecification(upperValue, UMLPackage.eINSTANCE.getLiteralUnlimitedNatural_Value(), domain);
}
if(event.getSource() == lowerValueSpecification || event.getSource() == upperValueSpecification) {
fireChange = true;
}
if(fireChange) {
final Object value = getValue();
fireValueChange(new ValueDiff() {
@Override
public Object getOldValue() {
return null; //Unknown
}
@Override
public Object getNewValue() {
return value;
}
});
}
}
@Override
public synchronized void dispose() {
lowerValue.removeChangeListener(this);
upperValue.removeChangeListener(this);
if(lowerValueSpecification != null) {
lowerValueSpecification.removeChangeListener(this);
lowerValueSpecification.dispose();
}
if(upperValueSpecification != null) {
upperValueSpecification.removeChangeListener(this);
upperValueSpecification.dispose();
}
lowerValue.dispose();
upperValue.dispose();
lowerBound.dispose();
upperBound.dispose();
super.dispose();
}
public Object getValueType() {
return String.class;
}
@Override
protected String doGetValue() {
int upper, lower;
upper = lower = 0;
Object lowerValue = lowerBound.getValue();
Object upperValue = upperBound.getValue();
lower = (Integer)lowerValue;
upper = (Integer)upperValue;
if(lower == 0 && upper == -1) {
return ANY;
} else if(lower == 0 && upper == 1) {
return OPTIONAL;
} else if(lower == 1 && upper == -1) {
return ONE_OR_MORE;
} else if(lower == 1 && upper == 1) {
return ONE;
} else {
return lower + SEPARATOR + (upper < 0 ? STAR : upper);
}
}
@Override
protected void doSetValue(Object value) {
Command command = getCommand(value);
domain.getCommandStack().execute(command);
}
private Command getSetCommand(EStructuralFeature feature, int value) {
IElementEditService provider = ElementEditServiceUtils.getCommandProvider(eObject);
if(provider != null) {
SetRequest request = new SetRequest(eObject, feature, value);
ICommand createGMFCommand = provider.getEditCommand(request);
Command emfCommand = new GMFtoEMFCommandWrapper(createGMFCommand);
return emfCommand;
}
return null;
}
public Command getCommand(Object value) {
int lower, upper;
String val = (String)value;
if(val.equals(ANY) || val.equals(STAR)) {
lower = 0;
upper = -1;
} else if(val.equals(OPTIONAL)) {
lower = 0;
upper = 1;
} else if(val.equals(ONE_OR_MORE)) {
lower = 1;
upper = -1;
} else if(val.equals(ONE)) {
lower = 1;
upper = 1;
} else {
if(val.matches("^[0-9]+(..[0-9*]+)?$")) { //$NON-NLS-1$
try {
if(val.contains(SEPARATOR)) {
lower = Integer.parseInt(val.substring(0, val.indexOf(SEPARATOR)));
String upperString = val.substring(val.indexOf(SEPARATOR) + SEPARATOR.length(), val.length());
if(STAR.equals(upperString)) {
upper = -1;
} else {
upper = Integer.parseInt(upperString);
}
} else {
lower = Integer.parseInt(val);
upper = Integer.parseInt(val);
}
} catch (NumberFormatException ex) {
return UnexecutableCommand.INSTANCE; //Invalid multiplicity
}
} else {
return UnexecutableCommand.INSTANCE; //Invalid multiplicity
}
}
if((upper > 0 && upper < lower) || upper == 0) {
return UnexecutableCommand.INSTANCE;
}
try {
Command lowerSetCommand = getSetCommand(lowerFeature, lower);
Command upperSetCommand = getSetCommand(upperFeature, upper);
CompoundCommand command = new CompoundCommand("Set multiplicity");
command.append(lowerSetCommand);
command.append(upperSetCommand);
return command;
} catch (Exception ex) {
Activator.log.error(ex);
}
return UnexecutableCommand.INSTANCE;
}
public AggregatedObservable aggregate(IObservable observable) {
try {
return new AggregatedPapyrusObservableValue(domain, this, observable);
} catch (IllegalArgumentException ex) {
return null; //The observable cannot be aggregated
}
}
public boolean hasDifferentValues() {
return false;
}
}