blob: f6b48e504043614e35a25dd507d69677b9dcba20 [file] [log] [blame]
/*******************************************************************************
* Copyright (c) 2011, 2013 Formal Mind GmbH and University of Dusseldorf.
*
* 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:
* Michael Jastram - initial API and implementation
******************************************************************************/
package org.eclipse.rmf.reqif10.pror.util;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import org.eclipse.emf.common.command.Command;
import org.eclipse.emf.common.command.CommandWrapper;
import org.eclipse.emf.common.command.CompoundCommand;
import org.eclipse.emf.common.command.UnexecutableCommand;
import org.eclipse.emf.common.notify.AdapterFactory;
import org.eclipse.emf.common.notify.Notification;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.common.util.TreeIterator;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.EReference;
import org.eclipse.emf.ecore.EStructuralFeature;
import org.eclipse.emf.ecore.impl.ENotificationImpl;
import org.eclipse.emf.ecore.impl.EObjectImpl;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.EMFEditPlugin;
import org.eclipse.emf.edit.command.AddCommand;
import org.eclipse.emf.edit.command.CommandParameter;
import org.eclipse.emf.edit.command.RemoveCommand;
import org.eclipse.emf.edit.command.SetCommand;
import org.eclipse.emf.edit.domain.EditingDomain;
import org.eclipse.emf.edit.provider.ComposeableAdapterFactory;
import org.eclipse.emf.edit.provider.ComposedImage;
import org.eclipse.emf.edit.provider.IItemLabelProvider;
import org.eclipse.emf.edit.provider.IItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.ItemPropertyDescriptor;
import org.eclipse.emf.edit.provider.ItemProviderAdapter;
import org.eclipse.rmf.reqif10.AttributeDefinition;
import org.eclipse.rmf.reqif10.AttributeValue;
import org.eclipse.rmf.reqif10.DatatypeDefinition;
import org.eclipse.rmf.reqif10.Identifiable;
import org.eclipse.rmf.reqif10.ReqIF;
import org.eclipse.rmf.reqif10.ReqIF10Factory;
import org.eclipse.rmf.reqif10.ReqIF10Package;
import org.eclipse.rmf.reqif10.ReqIFContent;
import org.eclipse.rmf.reqif10.SpecElementWithAttributes;
import org.eclipse.rmf.reqif10.SpecHierarchy;
import org.eclipse.rmf.reqif10.SpecObject;
import org.eclipse.rmf.reqif10.SpecRelation;
import org.eclipse.rmf.reqif10.SpecType;
import org.eclipse.rmf.reqif10.Specification;
import org.eclipse.rmf.reqif10.common.util.ReqIF10Util;
import org.eclipse.rmf.reqif10.pror.configuration.ProrPresentationConfiguration;
import org.eclipse.rmf.reqif10.pror.configuration.provider.ProrPresentationConfigurationItemProvider;
import org.eclipse.rmf.reqif10.pror.provider.SpecElementWithAttributesItemProvider;
import org.eclipse.rmf.reqif10.util.ReqIF10Switch;
/**
* A Class full of tools for PorR-Programming. Note that you find more tools in
* {@link ReqifUtil}, which are independent from ProR.
*
* @author jastram
*
*/
public final class ProrUtil {
/**
* This class is not designed to be instantiated.
*/
private ProrUtil() {
throw new InstantiationError(
"This class is not designed to be instantiated.");
}
/**
* Creates Properties for the attributes of the
* {@link SpecElementWithAttributes}. This essentially allows us to handle
* the Attributes from the associated {@link SpecType} as properties. This
* applies to four types of Objects (TODO not all are implemented yet):
* {@link SpecObject}, {@link SpecRelation}, {@link Specification} and
* SpecGroup.
* <p>
* As a notable piece of information, the AttributeDefinition's ID is kept
* in the descriptor's "description" field.
*/
public static void addAttributePropertyDescriptor(
final SpecElementWithAttributes specElement,
ItemProviderAdapter provider,
List<IItemPropertyDescriptor> itemPropertyDescriptors) {
SpecType type = ReqIF10Util.getSpecType(specElement);
// No type - no additional descriptors
if (type == null)
return;
// Add one descriptor per definition
for (AttributeDefinition definition : type.getSpecAttributes()) {
final String label = definition.getLongName() != null ? definition
.getLongName() : "UNNAMED (" + definition.getIdentifier()
+ ")";
final String category = type.getLongName() != null ? type
.getLongName() : "<UNNAMED TYPE>";
IItemPropertyDescriptor descriptor = new ItemPropertyDescriptor(
provider.getAdapterFactory(),
label, // DisplayName
definition.getIdentifier(), // Description
ReqIF10Package.Literals.SPEC_ELEMENT_WITH_ATTRIBUTES__VALUES,
true, category);
itemPropertyDescriptors.add(descriptor);
}
}
/**
* Sets the value of the given {@link AttributeValue}. This helper method
* exists to work around the lack of inheritance in the
* {@link AttributeValue} setValue() infrastructure.
*/
public static void setTheValue(AttributeValue av, Object value,
EditingDomain ed) {
EStructuralFeature feature = ReqIF10Util.getTheValueFeature(av);
Command cmd = SetCommand.create(ed, av, feature, value);
ed.getCommandStack().execute(cmd);
}
/**
* Sets the value of the given {@link AttributeValue}. This helper method
* exists to work around the lack of inheritance in the
* {@link AttributeValue} setValue() infrastructure. In addition, it takes a
* {@link SpecHierarchy} as an argument that is being used as the affected
* object. And last, the value may not be connected to its parent, in which
* case this method takes care of that as well.
*/
public static void setTheValue(final AttributeValue av, Object value,
Object parent, final Object affectedObject, EditingDomain ed) {
// The Command that sets the value
EStructuralFeature feature = ReqIF10Util.getTheValueFeature(av);
Command cmd = SetCommand.create(ed, av, feature, value);
// If necessary, create a command to attach the value to its parent
if (av.eContainer() == null) {
Command setValueCmd = cmd;
Command addValueToParentCmd = AddCommand
.create(ed,
parent,
ReqIF10Package.Literals.SPEC_ELEMENT_WITH_ATTRIBUTES__VALUES,
av);
cmd = new CompoundCommand();
((CompoundCommand) cmd).append(addValueToParentCmd);
((CompoundCommand) cmd).append(setValueCmd);
}
// Wrap it all, to get the correct affected objects.
Command cmd2 = new CommandWrapper(cmd) {
@SuppressWarnings({ "unchecked", "rawtypes" })
@Override
public Collection<?> getAffectedObjects() {
List list = new ArrayList();
list.add(affectedObject);
return list;
}
};
ed.getCommandStack().execute(cmd2);
}
/**
* Sets the value on the given element, provided the value exists.
*
* @param specObject
* @param definition
* @param value
*
* @return true if the value was set, otherwise false.
*/
public static boolean setTheValue(SpecObject specObject,
DatatypeDefinition definition, Object value,
SpecElementWithAttributes parent, EditingDomain ed) {
EList<AttributeValue> list = specObject.getValues();
for (AttributeValue av : list) {
if (definition.equals(ReqIF10Util.getDatatypeDefinition(av))) {
ProrUtil.setTheValue(av, value, parent, specObject, ed);
return true;
}
}
return false;
}
/**
* Tries to retrieve the ItemProvider for the given object, using the
* {@link AdapterFactory}. May return null.
*
* @return The AdpaterFactory for object or null if none found.
*/
public static ItemProviderAdapter getItemProvider(
AdapterFactory adapterFactory, Object object) {
if (adapterFactory instanceof ComposeableAdapterFactory) {
adapterFactory = ((ComposeableAdapterFactory) adapterFactory)
.getRootAdapterFactory();
}
// Workaround - we simply try to retrieve an IItemLabelProvider.
return (ItemProviderAdapter) adapterFactory.adapt(object,
IItemLabelProvider.class);
}
public static void visitAllSpecElementsWithAttributes(ReqIF reqif,
ReqIF10Switch<?> visitor) {
for (TreeIterator<Object> i = EcoreUtil.getAllContents(reqif, false); i
.hasNext();) {
Object obj = i.next();
if (obj instanceof SpecElementWithAttributes) {
visitor.doSwitch((EObject) obj);
}
}
}
/**
* Collects NewChildDescriptors for the creation of new Elements for
* SpecElements that are already typed. These should be hooked into the
* various methods of the ItemProviders for SpecObject, SpecHierarchy,
* Specification and SpecRelation.
* <p>
*
* We provide the parent as the owner. This is either {@link ReqIfContent},
* but could also be a {@link Specification} or {@link SpecHierarchy}.
* <p>
*
* As the feature we provide the actual feature for the data object.
* <p>
*
* The value is a class derived from {@link SpecType} (e.g. SpecObjectType).
* <p>
*
* These parameters will be handed to
* {@link #collectNewChildDescriptorsForTypeCreators(Collection, Object, EStructuralFeature, Class)}
* and eventually processed by
* {@link SpecElementWithAttributesItemProvider#createAddCommand()}.
*
* @param newChildDescriptors
* @param object
*/
public static void collectNewChildDescriptorsForTypeCreators(
Collection<Object> newChildDescriptors, Object object,
EStructuralFeature feature, Class<?> specTypeClass) {
// Add a Descriptor for each SpecType
EList<SpecType> specTypes = ReqIF10Util.getReqIF(object)
.getCoreContent().getSpecTypes();
for (final SpecType specType : specTypes) {
if (specTypeClass.isAssignableFrom(specType.getClass())) {
newChildDescriptors.add(new CommandParameter(object, feature,
specType));
}
}
}
/**
* Creates a command for adding a typed SpecElement. This should work no
* matter what the type is. A correct icon is provided. We return a
* {@link CompoundCommand}, so additional commands can be appended (e.g. for
* SpecHierarchies). The result index can be adjusted.
* <p>
*
* @param parent
* The parent of newSpecLement
* @param childFeature
* The Feature for adding newSpecElement to parent
* @param newSpecElement
* an instance of {@link SpecElementWithAttributes} that will be
* typed.
* @param typeFeature
* the feature for adding specType to newSpecElement
* @param specType
* an instance of the specType to be used for newSpecElement
* @param index
* The index for the position of newSpecElement under parent
* @param resultIndex
* The index of the command to be used for affected Elements (the
* resulting CompoundCommand already contains 3 commands)
* @param domain
* the EditingDomain
* @param adapterFactory
* the AdapterFactory
* @return
*/
public static CompoundCommand createAddTypedElementCommand(Object parent,
EReference childFeature, Identifiable newSpecElement,
EReference typeFeature, SpecType specType, int index,
int resultIndex, EditingDomain domain, AdapterFactory adapterFactory) {
ItemProviderAdapter newElementItemProvider = ProrUtil.getItemProvider(
adapterFactory, newSpecElement);
Object icon = newElementItemProvider.getImage(newSpecElement);
final CompoundCommand cmd = createCompoundCommandWithAddIcon(icon,
resultIndex);
cmd.append(AddCommand.create(domain, parent, childFeature,
newSpecElement, index));
HashSet<SpecType> typeCollection = new HashSet<SpecType>();
typeCollection.add((SpecType) specType);
CommandParameter typeParameter = new CommandParameter(newSpecElement,
typeFeature, typeCollection);
// TODO doesn't feel right
cmd.append(newElementItemProvider.createCommand(parent, domain,
AddCommand.class, typeParameter));
String name = newSpecElement.getClass().getSimpleName();
name = name.length() > 4 ? name.substring(0, name.length() - 4) : name;
String label = name + " (" + ((SpecType) specType).getLongName() + ")";
cmd.setLabel(label);
cmd.setDescription("Adding " + label);
return cmd;
}
/**
* Builds a CompoundCommand that has the given icon, with an overlay plus
* (+) to indicate that the command executes an addition.
*
* @param icon
* @return
*/
public static CompoundCommandActionDelegate createCompoundCommandWithAddIcon(
final Object icon, int resultIndex) {
return new CompoundCommandActionDelegate(resultIndex) {
@Override
public Object getImage() {
List<Object> images = new ArrayList<Object>();
images.add(icon);
images.add(EMFEditPlugin.INSTANCE
.getImage("full/ovr16/CreateChild"));
return new ComposedImage(images) {
@Override
public List<Point> getDrawPoints(Size size) {
List<Point> result = super.getDrawPoints(size);
result.get(1).x = size.width - 7;
return result;
}
};
}
};
}
/**
* @param adapterFactory
* @return the handle drag and drop command from presentation plugin or null
* if no plugin can handle the operation.
*/
public static Command getPresentationHandleDragAndDropCommand(
EditingDomain domain, Object owner, float location, int operations,
int operation, java.util.Collection<?> collection,
AdapterFactory adapterFactory) {
// See whether a Presentation feels responsible.
Set<PresentationEditInterface> ips = ConfigurationUtil
.getPresentationEditInterfaces(owner, adapterFactory);
for (PresentationEditInterface ip : ips) {
Command cmd = ip.handleDragAndDrop(collection, owner, domain,
operation);
if (cmd != null) {
return cmd;
}
}
return null;
}
/**
* This method creates the command for updating the {@link SpecType} of an
* {@link SpecElementWithUserDefinedAttributes}. It does <b>not</b> update
* the type itself; instead, it updates the values to match the type of the
* given specElement.
* <p>
*
* Using this command from the ItemProviders allows a clean change of type
* and values via the CommandStack.
* <p>
*
* BEHAVIOR: This command removes all non-matching values, but does not
* create new values. Therefore it may remove values, but never add values.
* <p>
*
* WATCH OUT: This method may return a command that is empty, which in turn
* isn't executable by default.
* <p>
*
* @return The Command that updates the Values
*/
public static CompoundCommand createValueAdjustCommand(
EditingDomain domain, SpecElementWithAttributes specElement,
Collection<AttributeDefinition> definitions) {
// Find values that are not needed any more.
HashSet<AttributeValue> existingObsoleteValues = new HashSet<AttributeValue>(
specElement.getValues());
// The list of types for the new values.
Set<AttributeDefinition> newDefs = new HashSet<AttributeDefinition>(
definitions);
// A CompoundCommand for removing values
CompoundCommand cmd = new CompoundCommand(
"Updating Type (and associated Values)");
// Iterate over the required attributes...
outer: for (AttributeDefinition newDef : newDefs) {
// ... and check for each whether it already exists:
for (AttributeValue value : specElement.getValues()) {
AttributeDefinition def = ReqIF10Util
.getAttributeDefinition(value);
if (def != null && def.equals(newDef)) {
// It does: Continue the outer loop
existingObsoleteValues.remove(value);
continue outer;
}
}
}
// If there are any values left, we need to remove them
for (AttributeValue value : existingObsoleteValues) {
cmd.append(RemoveCommand
.create(domain,
specElement,
ReqIF10Package.Literals.SPEC_ELEMENT_WITH_ATTRIBUTES__VALUES,
value));
}
return cmd;
}
/**
* Builds a command that creates new {@link SpecRelation}s between the given
* sources and target. Both, source and target can be {@link SpecObject}s
* and {@link SpecHierarchy}s.
*/
public static Command createCreateSpecRelationsCommand(
EditingDomain domain, Collection<?> sources, Object target) {
// Find the target SpecObject
SpecObject targetObject = null;
if (target instanceof SpecObject) {
targetObject = (SpecObject) target;
} else if (target instanceof SpecHierarchy) {
targetObject = ((SpecHierarchy) target).getObject();
}
if (targetObject == null) {
return UnexecutableCommand.INSTANCE;
}
ReqIFContent content = ReqIF10Util.getReqIF(targetObject)
.getCoreContent();
ArrayList<SpecRelation> relations = new ArrayList<SpecRelation>();
for (Object source : sources) {
if (source instanceof SpecHierarchy) {
source = ((SpecHierarchy) source).getObject();
}
if (source instanceof SpecObject) {
SpecObject sourceObject = (SpecObject) source;
SpecRelation relation = ReqIF10Factory.eINSTANCE
.createSpecRelation();
relation.setSource(sourceObject);
relation.setTarget(targetObject);
relations.add(relation);
}
}
return AddCommand.create(domain, content,
ReqIF10Package.Literals.REQ_IF_CONTENT__SPEC_RELATIONS,
relations);
}
/**
* This class reflectively looks for the given postfix and removes it from
* the classname of the given object. Should the result contain camel case,
* then spaces will be inserted.
* <p>
*
* If obj is itself a {@link Class}, its simple name is used directly.
*
* If the postfix does not match, the simple class name is returned.
* <p>
*
* If obj is null, the empty string is returned.
* <p>
*
* The idea is that in some places, it is convenient to extract information
* directly from the CamelCased classname, e.g. SpecRelationTypeItemProvider
* => "Spec Relation Type".
*/
public static String substractPrefixPostfix(Object obj, String prefix,
String suffix) {
if (obj == null) {
return "";
}
String className = obj instanceof Class ? ((Class<?>) obj)
.getSimpleName() : obj.getClass().getSimpleName();
if (!className.startsWith(suffix) && !className.endsWith(suffix)) {
return className;
}
String name = className.substring(prefix.length(), className.length()
- suffix.length());
StringBuilder sb = new StringBuilder();
for (int i = 0; i < name.length(); i++) {
char c = name.charAt(i);
if (i != 0 && Character.isUpperCase(c)) {
sb.append(' ');
}
sb.append(c);
}
return sb.toString();
}
/**
* This method must be called by all setType() calls of the subclasses, to
* set the values that correspond to the attributes of the type.
* <p>
*
* @param valueFeature
* the correct value from {@link ReqIFPackage}, e.g.
* {@link ReqIFPackage#SPEC_OBJECT__VALUES}.
*/
public static void updateValuesForCurrentType(SpecObject specObject) {
// First make sure all required values exist
HashSet<AttributeValue> existingRequiredValues = new HashSet<AttributeValue>(
specObject.getValues());
if (specObject.getType() != null) {
// Iterate over the required attributes...
outer: for (AttributeDefinition attrDefFromNewType : specObject
.getType().getSpecAttributes()) {
// ... and check for each whether it already exists:
for (AttributeValue value : specObject.getValues()) {
AttributeDefinition definition = ReqIF10Util
.getAttributeDefinition(value);
if (definition != null
&& definition.equals(attrDefFromNewType)) {
// It does: Continue the outer loop
existingRequiredValues.remove(value);
continue outer;
}
}
// The attribute is missing: Let's add it; but we can only add
// it, if a type is set.
AttributeValue value = ReqIF10Util
.createAttributeValue(attrDefFromNewType);
if (value != null) {
specObject.getValues().add(value);
if (((EObjectImpl) specObject).eNotificationRequired())
specObject.eNotify(new ENotificationImpl(
(EObjectImpl) specObject, Notification.ADD,
null, null, value));
}
// If there are any values left, we need to remove them
for (AttributeValue attributeValue : existingRequiredValues) {
specObject.getValues().remove(attributeValue);
}
}
} else {
// We don't do a thing: We leave the (now stale) Attributes. They
// will be removed if a new type is set.
}
}
/**
* Helper function for drag and drop operations: Tests if the element source
* may be dropped onto the target object.
*
* @param source
* @param target
* @return true if the drop should be accepted, false otherwise
*/
public static boolean isValidDrop(SpecHierarchy source, Object target) {
if (source == target) {
return false;
}
if (source.getChildren().contains(target)) {
return false;
}
for (EObject child : source.getChildren()) {
if (child instanceof SpecHierarchy) {
if (!isValidDrop((SpecHierarchy) child, target)) {
return false;
}
}
}
return true;
}
/**
* Retrieves the {@link ProrPresentationConfigurationItemProvider} for the
* given {@link ProrPresentationConfiguration} element.
*/
public static ProrPresentationConfigurationItemProvider getConfigItemProvider(
ProrPresentationConfiguration config, AdapterFactory adapterFactory) {
ProrPresentationConfigurationItemProvider itemprovider = (ProrPresentationConfigurationItemProvider) getItemProvider(
adapterFactory, config);
return itemprovider;
}
}