/** * Copyright (c) 2007 Borland Software Corporation * * 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: * bblajer - initial API and implementation */ package org.eclipse.gmf.runtime.lite.edit.parts.labels; import java.text.MessageFormat; import java.util.Collection; 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.common.notify.Notification; import org.eclipse.emf.common.util.BasicEList; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.ecore.EAttribute; import org.eclipse.emf.ecore.EClassifier; import org.eclipse.emf.ecore.EDataType; import org.eclipse.emf.ecore.EObject; import org.eclipse.emf.ecore.EStructuralFeature; import org.eclipse.emf.edit.command.AddCommand; import org.eclipse.emf.edit.command.RemoveCommand; import org.eclipse.emf.edit.command.SetCommand; import org.eclipse.emf.transaction.TransactionalEditingDomain; import org.eclipse.emf.transaction.util.TransactionUtil; import org.eclipse.gmf.internal.runtime.lite.Activator; import org.eclipse.gmf.runtime.lite.services.ParserUtil; import org.eclipse.jface.viewers.ICellEditorValidator; /** * An implementation of {@link ILabelTextDisplayer} that is based on a * number of {@link EStructuralFeature structural features} of the source object. */ public abstract class AbstractFeatureBasedLabelTextDisplayer extends AbstractLabelTextDisplayer implements ICellEditorValidator { private final EStructuralFeature[] myFeatures; protected static final String EMPTY_STRING = ""; //$NON-NLS-1$ public AbstractFeatureBasedLabelTextDisplayer(EStructuralFeature... features) { myFeatures = features; } public String getDisplayText(EObject source) { if (source == null) { return null; } Object[] values = getValues(source); if (values == null) { return null; } String result = buildDisplayText(values); if (result == null || result.length() == 0) { return null; } return result; } @Override public String getEditText(EObject source) { if (source == null) { return EMPTY_STRING; } Object[] values = getValues(source); if (values == null) { return EMPTY_STRING; } String result = buildEditText(values); if (result == null) { return EMPTY_STRING; } return result; } protected Object[] getValues(EObject source) { Object[] result = new Object[myFeatures.length]; try { for(int i = 0; i < myFeatures.length; i++) { result[i] = getValidValue(myFeatures[i], source.eGet(myFeatures[i])); if (result[i] == null) { return null; } } } catch (Exception e) { //This may happen e.g., if the source is an unresolved proxy. Activator.getDefault().logError("Exception occurred while building text for a label", e); return null; } return result; } /** * Allows the given value to be replaced. By default, <code>null</code> strings are converted to empty strings, * no other replacements are performed. Subclasses may extend or reimplement. */ protected Object getValidValue(EStructuralFeature feature, Object value) { if (value == null) { EClassifier type = feature.getEType(); if (type instanceof EDataType && String.class.equals(type.getInstanceClass())) { return EMPTY_STRING; } } return value; } public boolean isAffectingEvent(Notification notification) { if (notification == null || notification.isTouch()) { return false; } Object feature = notification.getFeature(); for (EStructuralFeature nextFeature : myFeatures) { if (nextFeature.equals(feature)) { return true; } } return false; } @SuppressWarnings("unchecked") @Override public Command getApplyCommand(EObject source, String newValue) { Object[] parsedValues; Object[] newValues; try { parsedValues = parseEditedValues(newValue); if (parsedValues == null || parsedValues.length != myFeatures.length) { return UnexecutableCommand.INSTANCE; } newValues = new Object[myFeatures.length]; for (int i = 0; i < myFeatures.length; i++) { newValues[i] = getValidNewValue(myFeatures[i], parsedValues[i]); } } catch (IllegalArgumentException e) { return UnexecutableCommand.INSTANCE; } TransactionalEditingDomain editingDomain = TransactionUtil.getEditingDomain(source); CompoundCommand result = new CompoundCommand(); for (int i = 0; i < myFeatures.length; i++) { if (myFeatures[i].isMany()) { EList valuesList = new BasicEList(); valuesList.addAll((Collection) source.eGet(myFeatures[i])); result.append(RemoveCommand.create(editingDomain, source, myFeatures[i], valuesList)); if (newValues[i] != null) { result.append(AddCommand.create(editingDomain, source, myFeatures[i], newValues[i])); } } else { result.append(SetCommand.create(editingDomain, source, myFeatures[i], newValues[i] == null ? SetCommand.UNSET_VALUE : newValues[i])); } } return result.unwrap(); } @Override public ICellEditorValidator getValidator() { return this; } public String isValid(Object value) { if (false == value instanceof String) { return "String value expected"; } String stringValue = (String) value; Object[] values; try { values = parseEditedValues(stringValue); } catch (IllegalArgumentException e) { return "Unable to parse input"; } if (values == null || values.length != myFeatures.length) { return "Unable to parse input"; } for(int i = 0; i < myFeatures.length; i++) { try { getValidNewValue(myFeatures[i], values[i]); } catch (IllegalArgumentException e) { MessageFormat.format("Invalid input at {0}: {1}", i, e.getLocalizedMessage()); } } return null; } /** * Allows the parsed value to be replaced (e.g., to match the type of the structural feature). * @throws IllegalArgumentException if the value cannot be parsed for the type of the given structural feature. */ protected Object getValidNewValue(EStructuralFeature structuralFeature, Object value) throws IllegalArgumentException { if (EMPTY_STRING.equals(value) && shouldReplaceEmptyStringsWithNulls(structuralFeature)) { return null; } if (structuralFeature instanceof EAttribute) { return ParserUtil.parseValue((EAttribute) structuralFeature, value); } return null; } /** * Returns whether empty strings should be replaced with <code>null</code>s when applied. */ protected boolean shouldReplaceEmptyStringsWithNulls(EStructuralFeature structuralFeature) { return !structuralFeature.isRequired(); } /** * Returns the features used by this displayer. */ protected EStructuralFeature[] getFeatures() { return myFeatures; } /** * Returns the text to be displayed by this label processor for the given values. */ protected abstract String buildDisplayText(Object[] featureValues); /** * Returns the initial edit text to be displayed by this label processor for the given values. */ protected abstract String buildEditText(Object[] featureValues); /** * Returns the values that should be set to the corresponding features. * @throws IllegalArgumentException If the given string is invalid. */ protected abstract Object[] parseEditedValues(String newString) throws IllegalArgumentException; }