| //------------------------------------------------------------------------------ |
| // Copyright (c) 2005, 2006 IBM Corporation and others. |
| // 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: |
| // IBM Corporation - initial implementation |
| //------------------------------------------------------------------------------ |
| package org.eclipse.epf.library.edit.validation; |
| |
| import java.util.ArrayList; |
| import java.util.Collection; |
| import java.util.Collections; |
| import java.util.HashSet; |
| import java.util.Iterator; |
| import java.util.List; |
| |
| import org.eclipse.core.runtime.IStatus; |
| import org.eclipse.core.runtime.Status; |
| import org.eclipse.emf.common.util.AbstractTreeIterator; |
| import org.eclipse.emf.ecore.EObject; |
| import org.eclipse.emf.ecore.EStructuralFeature; |
| import org.eclipse.epf.library.edit.IFilter; |
| import org.eclipse.epf.library.edit.LibraryEditPlugin; |
| import org.eclipse.epf.library.edit.LibraryEditResources; |
| import org.eclipse.epf.library.edit.util.ProcessUtil; |
| import org.eclipse.epf.library.edit.util.TngUtil; |
| import org.eclipse.epf.uma.Activity; |
| import org.eclipse.epf.uma.Artifact; |
| import org.eclipse.epf.uma.BreakdownElement; |
| import org.eclipse.epf.uma.CustomCategory; |
| import org.eclipse.epf.uma.Deliverable; |
| import org.eclipse.epf.uma.DescribableElement; |
| import org.eclipse.epf.uma.MethodLibrary; |
| import org.eclipse.epf.uma.Process; |
| import org.eclipse.epf.uma.UmaPackage; |
| import org.eclipse.epf.uma.VariabilityElement; |
| import org.eclipse.epf.uma.VariabilityType; |
| import org.eclipse.epf.uma.util.UmaUtil; |
| import org.eclipse.osgi.util.NLS; |
| |
| /** |
| * This class has static methods that check for cycles in the element dependency |
| * |
| * @author Phong Nguyen Le |
| * @since 1.0 |
| */ |
| public final class DependencyChecker { |
| private static final IStatus ERROR_STATUS = new Status(IStatus.ERROR, |
| LibraryEditPlugin.getDefault().getId(), 0, "", null); //$NON-NLS-1$ |
| |
| public static boolean newCheck = true; //temp flag, will be removed! |
| public static boolean newCheckAct = true; //temp flag, will be removed! |
| |
| /** |
| * Checks for the cycles in the dependency graph of the given feature if the |
| * given value is added/assigned to it. |
| * |
| * @param owner |
| * the owner of the feature |
| * @param feature |
| * the feature to check for circular dependency |
| * @param value |
| * the value to be added/assigned to the feature |
| * @return check status, OK if no cycle, ERROR if there will be circular |
| * dependency |
| */ |
| public static IStatus checkCircularDependency(EObject owner, |
| final EStructuralFeature feature, Object value) { |
| if (feature == UmaPackage.Literals.ACTIVITY__BREAKDOWN_ELEMENTS) { |
| if (value instanceof Activity) { |
| Activity activity = (Activity) owner; |
| VariabilityType type = activity.getVariabilityType(); |
| return checkCircularDependency(activity, (Activity) value, type); |
| } |
| } else if (feature == UmaPackage.Literals.VARIABILITY_ELEMENT__VARIABILITY_BASED_ON_ELEMENT) { |
| if (isCircularDependency((VariabilityElement) owner, |
| (VariabilityElement) value)) { |
| return ERROR_STATUS; |
| } |
| } else if (feature == UmaPackage.Literals.DELIVERABLE__DELIVERED_WORK_PRODUCTS) { |
| if (!checkCircularDeliverables((Deliverable)value, (Deliverable)owner)) { |
| return ERROR_STATUS; |
| } |
| } else { |
| if (feature.isMany()) { |
| Iterator iter = new AbstractTreeIterator(value) { |
| |
| /** |
| * Comment for <code>serialVersionUID</code> |
| */ |
| private static final long serialVersionUID = 0L; |
| |
| protected Iterator getChildren(Object object) { |
| if (feature.getContainerClass().isInstance(object)) { |
| EObject eObject = (EObject) object; |
| List list = (List) eObject.eGet(feature); |
| if (!list.isEmpty()) { |
| ArrayList children = new ArrayList(); |
| for (Iterator iterator = list.iterator(); iterator |
| .hasNext();) { |
| Object element = iterator.next(); |
| if (feature.getContainerClass().isInstance( |
| element)) { |
| children.add(element); |
| } |
| } |
| return children.iterator(); |
| } |
| } |
| return Collections.EMPTY_LIST.iterator(); |
| } |
| |
| }; |
| |
| while (iter.hasNext()) { |
| if (iter.next() == owner) { |
| return ERROR_STATUS; |
| } |
| } |
| |
| } else { |
| if (feature.getContainerClass().isInstance(value)) { |
| EObject v = (EObject) value; |
| do { |
| if (v == owner) { |
| return ERROR_STATUS; |
| } |
| Object o = v.eGet(feature); |
| if (feature.getContainerClass().isInstance(o)) { |
| v = (EObject) o; |
| } else { |
| v = null; |
| } |
| } while (v != null); |
| } |
| } |
| } |
| |
| return Status.OK_STATUS; |
| } |
| |
| /** |
| * Checks for circular dependency if the given activity is applied to the |
| * given target activity via extend or copy. |
| * |
| * @param activity activity to apply |
| * @param target target activity to add extension or copy of the given activity |
| * @return status of this check |
| */ |
| public static IStatus checkCircularDependency(Activity activity, Activity target) { |
| if (isParent(activity, target)) { |
| Object[] args = { target.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.activity_variability_error_msg1, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| Process process = TngUtil.getOwningProcess(target); |
| if(!newCheckAct && hasCyclicDependency(activity, process) || |
| newCheckAct && sourceReachableByTarget(activity, target)) { |
| Object[] args = { activity.getName(), process.getName() }; |
| String message = NLS.bind( |
| LibraryEditResources.apply_pattern_error_msg, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| else if(ProcessUtil.hasContributorOrReplacer(activity)) { |
| Object[] args = { activity.getName(), process.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.apply_pattern_error_msg1, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| |
| } |
| return Status.OK_STATUS; |
| } |
| |
| private static boolean hasCyclicDependency(Activity activity, Process process) { |
| Iterator iter = new AbstractTreeIterator(activity) { |
| |
| /** |
| * Comment for <code>serialVersionUID</code> |
| */ |
| private static final long serialVersionUID = 0L; |
| |
| protected Iterator getChildren(Object object) { |
| if (object instanceof Activity) { |
| ArrayList children = new ArrayList(); |
| for (Iterator iterator = ((Activity) object) |
| .getBreakdownElements().iterator(); iterator |
| .hasNext();) { |
| Object element = iterator.next(); |
| if (element instanceof VariabilityElement) { |
| children.add(element); |
| } |
| } |
| return children.iterator(); |
| } |
| return Collections.EMPTY_LIST.iterator(); |
| } |
| |
| }; |
| |
| while (iter.hasNext()) { |
| VariabilityElement ve = (VariabilityElement) iter.next(); |
| VariabilityElement base = ve.getVariabilityBasedOnElement(); |
| VariabilityType vType = ve.getVariabilityType(); |
| if (base != null && (vType == VariabilityType.EXTENDS)) { |
| Process proc = TngUtil |
| .getOwningProcess((BreakdownElement) base); |
| if (proc == process) { |
| return true; |
| } |
| } |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Checks if the given parent is a super activity of child. |
| * |
| * @param parent |
| * @param child |
| * @return |
| */ |
| public static boolean isParent(Activity parent, BreakdownElement child) { |
| for (Activity act = child.getSuperActivities(); act != null; act = act |
| .getSuperActivities()) { |
| if (act == parent) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks for cycles in dependency path of the given activity |
| * <code>act</code> if <code>base</code> is the base activity of |
| * <code>act</code> for given variability type |
| * |
| * @param act |
| * @param base |
| * @param type |
| * @return |
| */ |
| public static IStatus checkCircularDependency(Activity act, Activity base, |
| VariabilityType type) { |
| return checkCircularDependency(act, base, type, false); |
| } |
| |
| public static IStatus checkCircularDependencyAfterFilterSelection(Activity act, Activity base, |
| VariabilityType type) { |
| return checkCircularDependency(act, base, type, true); |
| } |
| |
| private static IStatus checkCircularDependency(Activity act, Activity base, |
| VariabilityType type, boolean filtering) { |
| if((base instanceof Process) |
| && (type == VariabilityType.REPLACES) && !(act instanceof Process)) { |
| String message = LibraryEditResources.activity_variability_error_msg3; |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| else if (isParent(base, act)) { |
| Object[] args = { act.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.activity_variability_error_msg1, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| else if (newCheckAct && filtering) { |
| IFilter filter = getCircularDependencyCheckFilter(act); |
| if (! filter.accept(base)) { |
| Object[] args = { act.getName(), base.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.activity_variability_error_msg2, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| return Status.OK_STATUS; |
| } |
| else if (isCircularDependency(act, base)) { |
| Object[] args = { act.getName(), base.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.activity_variability_error_msg2, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| // block for children |
| else if (ProcessUtil.hasContributorOrReplacer(base) |
| && type == VariabilityType.EXTENDS) { |
| Object[] args = { act.getName(), base.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.activity_variability_error_msg, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| Process process = TngUtil.getOwningProcess(act); |
| if(process != null && hasCyclicDependency(base, process)) { |
| Object[] args = { act.getName(), base.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.activity_variability_error_msg2, |
| args); |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| |
| /** |
| * Check for circular dependency if <code>base</code> is the base element |
| * of the given <code>element</code>. |
| * |
| * @param base |
| * base element |
| * @return status which indicates circular depenedency is detected or not |
| */ |
| private static boolean isCircularDependency(VariabilityElement element, |
| VariabilityElement base) { |
| // standard check |
| // |
| VariabilityType type = base.getVariabilityType(); |
| while (type != VariabilityType.NA) { |
| VariabilityElement ve = base.getVariabilityBasedOnElement(); |
| if (ve != null && ve == element) |
| return true; |
| base = ve; |
| type = ve.getVariabilityType(); |
| |
| } |
| |
| return false; |
| } |
| |
| /** |
| * Check whether given deliverables leads to circular dependency |
| * |
| * @param toBePart |
| * @param deliverable |
| * @return <code>false</code> to indicate whether circular dependency is |
| * detected, <code>true</code> if there is no circular dependency |
| */ |
| public static boolean checkCircularDeliverables(Deliverable toBePart, |
| Deliverable deliverable) { |
| if (newCheck) { |
| return ! sourceReachableByTarget(toBePart, deliverable); |
| } |
| |
| if (toBePart == deliverable) { |
| return false; |
| } |
| java.util.List deliverables = toBePart.getDeliveredWorkProducts(); |
| if (deliverables != null && deliverables.size() > 0) { |
| if (deliverables.contains(deliverable)) |
| return false; |
| for (Iterator iterator = deliverables.iterator(); iterator |
| .hasNext();) { |
| Object obj = iterator.next(); |
| if (obj instanceof Deliverable) { |
| return checkCircularDeliverables((Deliverable) obj, |
| deliverable); |
| } |
| } |
| } |
| return true; |
| } |
| |
| /** |
| * Checks if the given CustomCategory is an ancestor of the given |
| * DescibableElement |
| * |
| * @param cc |
| * @param e |
| * @return |
| */ |
| public static boolean isAncessorOf(CustomCategory cc, DescribableElement e) { |
| Iterator iter = new AbstractTreeIterator(cc, false) { |
| private static final long serialVersionUID = 1L; |
| |
| protected Iterator getChildren(Object object) { |
| if (object instanceof CustomCategory) { |
| return ((CustomCategory) object).getCategorizedElements() |
| .iterator(); |
| } |
| return Collections.EMPTY_LIST.iterator(); |
| } |
| |
| }; |
| while (iter.hasNext()) { |
| if (iter.next() == e) { |
| return true; |
| } |
| } |
| return false; |
| } |
| |
| /** |
| * Checks circular dependency when moving a collection of artifact objects |
| * in "sourceElements" under "destination" |
| * |
| * @param destination |
| * @param sourceElements |
| * @return true if no circular dependency detected, otherwise false |
| */ |
| public static boolean checkCircularForArtifacts(Artifact destination, |
| Collection sourceElements) { |
| HashSet variantSet = new HashSet(); |
| collectVariantSet(destination, variantSet, VariabilityType.REPLACES); |
| collectVariantSet(destination, variantSet, VariabilityType.EXTENDS); |
| if (! checkCircularForArtifacts1(destination, sourceElements, variantSet)) { |
| return false; |
| } |
| return checkCircularForArtifacts2(destination, sourceElements); |
| } |
| |
| private static boolean checkCircularForArtifacts1(Artifact destination, |
| Collection sourceElements, HashSet variantSet) { |
| if (sourceElements == null) { |
| return true; |
| } |
| for (Iterator iter = sourceElements.iterator(); iter.hasNext();) { |
| Object obj = iter.next(); |
| if(obj instanceof Artifact){ |
| Artifact artifact = (Artifact) obj; |
| if (variantSet.contains(artifact)) { |
| return false; |
| } |
| if (! checkCircularForArtifacts1(destination, artifact.getContainedArtifacts(), variantSet)) { |
| return false; |
| } |
| } |
| } |
| return true; |
| } |
| |
| private static boolean checkCircularForArtifacts2(Artifact destination, |
| Collection sourceElements) { |
| for (Iterator iter = sourceElements.iterator(); iter.hasNext();) { |
| Object obj = iter.next(); |
| if (obj instanceof Artifact && sourceIsOrAboveMe((Artifact) obj, destination)) { |
| return false; |
| } |
| } |
| return true; |
| } |
| |
| private static boolean sourceIsOrAboveMe(Artifact source, Artifact me) { |
| if (source == me) { |
| return true; |
| } |
| Object obj = me.getContainerArtifact(); |
| if (obj instanceof Artifact && sourceIsOrAboveMe(source, (Artifact) obj)) { |
| return true; |
| } |
| obj = me.getVariabilityBasedOnElement(); |
| if (obj instanceof Artifact && sourceIsOrAboveMe(source, (Artifact) obj)) { |
| return true; |
| } |
| return false; |
| } |
| |
| //Collect all variants of "a" and "a"s ancestror Artifact objects |
| private static void collectVariantSet(Artifact a, HashSet variantSet, VariabilityType type) { |
| while (a != null) { |
| for (Iterator iter = TngUtil.getGeneralizers(a, type); iter.hasNext();) { |
| variantSet.add(iter.next()); |
| } |
| a = a.getContainerArtifact(); |
| } |
| } |
| |
| private static CircularDependencyCheck getCircularDependencyCheck(VariabilityElement ve, |
| boolean filter, boolean move, boolean dnd) { |
| MethodLibrary lib = UmaUtil.getMethodLibrary(ve); |
| DependencyInfoMgr depInfoMgr = new DependencyInfoMgr(lib); |
| depInfoMgr.setDndBit(dnd); |
| return new CircularDependencyCheck(depInfoMgr, ve, filter, move || dnd); |
| } |
| |
| private static boolean sourceReachableByTarget(VariabilityElement source, VariabilityElement target) { |
| return getCircularDependencyCheck(source, false, false, false).reachableBy(target); |
| } |
| |
| /** |
| * return a circular dependency check filter |
| */ |
| public static IFilter getCircularDependencyCheckFilter(VariabilityElement ve) { |
| MethodLibrary lib = UmaUtil.getMethodLibrary(ve); |
| DependencyInfoMgr depInfoMgr = new DependencyInfoMgr(lib); |
| return new CircularDependencyCheckFilter(depInfoMgr, ve); |
| } |
| |
| //Return true if no circular detected |
| public static boolean checkCircularForMovingVariabilityElement(VariabilityElement destination, |
| Collection sourceElements) { |
| return checkCircularForMovingVariabilityElement(destination, sourceElements, false); |
| } |
| |
| public static boolean checkCircularForMovingVariabilityElement(VariabilityElement destination, |
| Collection sourceElements, boolean dnd) { |
| if (! DependencyInfoMgr.VeToCheck(destination)) { |
| return true; |
| } |
| |
| CircularDependencyCheck check = getCircularDependencyCheck(destination, false, true, dnd); |
| for (Iterator it = sourceElements.iterator(); it.hasNext();) { |
| if (! check.accept(it.next())) { |
| return false; |
| } |
| } |
| |
| return true; |
| } |
| |
| public static IStatus checkCircularForApplyingVariabilityElement(VariabilityElement base, |
| VariabilityElement ve, boolean dnd) { |
| boolean ok = checkCircularForMovingVariabilityElement(base, Collections.singletonList(ve), true); |
| if (!ok) { |
| Object[] args = { ve.getName(), base.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.variability_error_msg, |
| args); |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| public static IStatus checkCircularDependencyAfterFilterSelection(VariabilityElement ve, VariabilityElement base) { |
| IFilter filter = getCircularDependencyCheckFilter(ve); |
| if (! filter.accept(base)) { |
| Object[] args = { ve.getName(), base.getName() }; |
| String message = NLS |
| .bind( |
| LibraryEditResources.variability_error_msg, |
| args); //Not right message, need to add a new resouce string later! |
| |
| return new Status(IStatus.ERROR, LibraryEditPlugin.getDefault() |
| .getId(), 0, message, null); |
| } |
| return Status.OK_STATUS; |
| } |
| |
| } |