blob: dd34b43df14d72a07af43dcc6e68575104e71361 [file] [log] [blame]
//------------------------------------------------------------------------------
// 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_LITERAL)) {
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_LITERAL) && !(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_LITERAL) {
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_LITERAL) {
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_LITERAL);
collectVariantSet(destination, variantSet, VariabilityType.EXTENDS_LITERAL);
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 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;
}
}