/******************************************************************************* * 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; } }