blob: f25b32612ea5c57734d87da6172eb7524e367820 [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.configuration;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.eclipse.emf.common.util.EList;
import org.eclipse.emf.ecore.EObject;
import org.eclipse.emf.ecore.util.EcoreUtil;
import org.eclipse.emf.edit.ui.provider.AdapterFactoryContentProvider;
import org.eclipse.epf.library.IConfigurationClosure;
import org.eclipse.epf.library.IConfigurationManager;
import org.eclipse.epf.library.LibraryPlugin;
import org.eclipse.epf.library.LibraryResources;
import org.eclipse.epf.library.LibraryService;
import org.eclipse.epf.library.edit.util.TngUtil;
import org.eclipse.epf.library.services.DependencyManager;
import org.eclipse.epf.library.services.ElementDependency;
import org.eclipse.epf.library.services.ElementReference;
import org.eclipse.epf.library.services.PackageReference;
import org.eclipse.epf.library.util.LibraryUtil;
import org.eclipse.epf.uma.Activity;
import org.eclipse.epf.uma.BreakdownElement;
import org.eclipse.epf.uma.MethodConfiguration;
import org.eclipse.epf.uma.MethodElement;
import org.eclipse.epf.uma.MethodLibrary;
import org.eclipse.epf.uma.MethodPackage;
import org.eclipse.epf.uma.MethodPlugin;
import org.eclipse.epf.uma.ProcessComponent;
import org.eclipse.ui.views.properties.IPropertyDescriptor;
import org.eclipse.ui.views.properties.IPropertySource;
/**
* A method configuration closure.
*
* @author Jinhua Xi
* @author Kelvin Low
* @since 1.0
*/
public class ConfigurationClosure implements IConfigurationClosure {
// If true, generate debug traces.
protected static boolean debug = LibraryPlugin.getDefault().isDebugging();
protected MethodConfiguration config = null;
protected MethodLibrary library = null;
protected IConfigurationManager configManager = null;
protected DependencyManager dependencyManager = null;
// Node change information. The object are the model objects
// check the linked objects in needed
protected List selected = new ArrayList();
// A map of invalid nodes to ElementDependencyError objects.
protected Map invalidNodesMap = new HashMap();
protected List changedNodes = new ArrayList();
/**
* Creates a new instance.
*
* @param config
* A method configuration.
*/
public ConfigurationClosure(MethodConfiguration config) {
this.config = config;
configManager = LibraryService.getInstance().getConfigurationManager(
config);
if (configManager != null) {
library = configManager.getMethodLibrary();
dependencyManager = configManager.getDependencyManager();
}
// cleanup the old status and rebuild the list
selected.clear();
changedNodes.clear();
invalidNodesMap.clear();
// configuration changed, re-build the selection list
try {
buildList(library);
} catch (Exception ex) {
ex.printStackTrace();
}
}
/**
* Returns the method configuration manager.
*
* @return A <code>ConfigurationManager</code>.
*/
public IConfigurationManager getConfigurationManager() {
return configManager;
}
/**
* Returns the method configuration.
*
* @return A <code>MethodConfiguration</code>.
*/
public MethodConfiguration getConfiguration() {
return config;
}
/**
* Returns the containining method library.
*
* @return A <code>MethodConfiguration</code>.
*/
public MethodLibrary getLibrary() {
return library;
}
/**
* Builds the selection list based on the method configuration.
*
* @param input
*/
private void buildList(EObject input) {
// Validate the method configuration.
LibraryUtil.validateMethodConfiguration(config);
selected.addAll(config.getMethodPluginSelection());
selected.addAll(config.getMethodPackageSelection());
// Re-build the selections to auto add the process packages.
setSelections(selected.toArray());
}
/**
* Sets the method plug-ins and packages selection.
*
* @param elements
* An array of method plug-ins and packages.
*/
public void setSelections(Object[] elements) {
if (elements == null) {
return;
}
// Cleanup the old status and rebuild the list.
selected.clear();
changedNodes.clear();
invalidNodesMap.clear();
beginUpdate();
for (int i = 0; i < elements.length; i++) {
Object e = elements[i];
if (!selected.contains(e)) {
if ((e instanceof EObject)) {
add((EObject) e, false);
// If the selected element is a process component,
// select all the process packages that contains the
// activities.
if (e instanceof ProcessComponent) {
selectProcessPackages(((ProcessComponent) e)
.getProcess());
}
} else {
// The selected element is a UI folder.
selected.add(e);
}
}
}
endUpdate();
}
private void selectProcessPackages(Activity a) {
if (a == null) {
return;
}
for (Iterator it = a.getBreakdownElements().iterator(); it.hasNext();) {
BreakdownElement e = (BreakdownElement) it.next();
Object pkg = e.eContainer();
if (!selected.contains(pkg)) {
selected.add(pkg);
changedNodes.add(pkg);
}
if (e instanceof Activity) {
selectProcessPackages((Activity) e);
}
}
}
/**
* Adds a method plug-in or package to the closure.
*
* @param element
* A method element.
* @param addChildren
* if <code>true</code>, add all child method elements.
*/
private void add(EObject element, boolean addChildren) {
if (!LibraryUtil.selectable(element)) {
return;
}
if (!selected.contains(element)) {
selected.add(element);
// Save the changed nodes so that we can update the status later.
addChanged(element);
if (element instanceof MethodPlugin) {
selectSystemPackages((MethodPlugin) element);
}
}
// Add the parent method element as well.
EObject parent = element.eContainer();
if ((parent != null) && !selected.contains(parent)) {
add(parent, false);
}
// Add children as needed.
if (addChildren) {
EList elements = element.eContents();
if (elements != null) {
for (Iterator it = elements.iterator(); it.hasNext();) {
EObject child = (EObject) it.next();
add(child, true);
}
}
}
}
private void addChanged(Object element) {
if (!changedNodes.contains(element)) {
changedNodes.add(element);
}
}
private void selectSystemPackages(MethodPlugin plugin) {
List pkgs = TngUtil.getAllSystemPackages(plugin);
for (Iterator it = pkgs.iterator(); it.hasNext();) {
EObject pkg = (EObject) it.next();
add(pkg, false);
}
}
/**
* Checks whether a method plug-in or package is selected.
*
* @return <code>true</code> if the given element is selected.
*/
public boolean isSelected(Object input) {
if ((input instanceof MethodLibrary) || input == config
|| selected.contains(input)) {
return true;
}
return false;
}
/**
* Gets the element dependency error for a method element.
*
* @element A method element.
*
* @return An <code>ElementDependencyError</code>.
*/
public ElementDependencyError getError(Object element) {
return getError(element, false);
}
private ElementDependencyError getError(Object element, boolean create) {
ElementDependencyError error = (ElementDependencyError) invalidNodesMap
.get(element);
if (error == null && create) {
error = new ElementDependencyError(element);
invalidNodesMap.put(element, error);
}
return error;
}
/**
* check if there is error in this configuration closure
*
* @return boolean
*/
public boolean hasError() {
for (Iterator it = invalidNodesMap.values().iterator(); it.hasNext();) {
ElementDependencyError error = (ElementDependencyError) it.next();
if (error.isError()) {
return true;
}
}
return false;
}
/**
* check if there is any problem (error/warning) with this configuration closure.
* @return boolean
*/
public boolean hasProblem() {
return invalidNodesMap.size() > 0;
}
/**
* Returns all the errors.
*
* @return A a list of <code>ErrorInfo</code>.
*/
public List getAllErrors() {
List errors = new ArrayList();
for (Iterator it = invalidNodesMap.values().iterator(); it.hasNext();) {
ElementDependencyError error = (ElementDependencyError) it.next();
errors.addAll(error.getAll());
}
return errors;
}
/**
* Returns all the dependency errors.
*
* @return An array of <code>ElementDependencyError</code>
*/
public Object[] getDependencyErrors() {
return invalidNodesMap.values().toArray();
}
/**
* Returns all the invalid elements.
*
* @return A list of invalid elements.
*/
public List getInvalidElements() {
return new ArrayList(invalidNodesMap.keySet());
}
private void removeError(Object element) {
if (invalidNodesMap.containsKey(element)) {
invalidNodesMap.remove(element);
// Error status changed, add it to the changed list.
if (!changedNodes.contains(element)) {
changedNodes.add(element);
}
}
}
/**
* Returns all the changed elements. These are elements whose check states
* have changed or whose image have changed due to error.
*
* @return A list of changed method elements.
*/
public List getChangedElements() {
List items = new ArrayList(changedNodes);
for (Iterator it = invalidNodesMap.keySet().iterator(); it.hasNext();) {
Object item = it.next();
if (!items.contains(item)) {
items.add(item);
}
}
return items;
}
/**
* Returns the method plug-ins and packages selection.
*
* @return An array of method plug-ins and packages.
*/
public Object[] getSelection() {
return selected.toArray();
}
/**
* begin the closure update.
*
*/
public void beginUpdate() {
changedNodes.clear();
}
/**
* end the closure update.
*
*/
public void endUpdate() {
// Process the changed elements.
// Note: additional elements may be added to the changesNodes due to
// error status change but only the original changed elements need to be
// processed.
for (Iterator it = new ArrayList(changedNodes).iterator(); it.hasNext();) {
Object changedElement = it.next();
ElementDependency dependency = dependencyManager
.getDependency((MethodElement) changedElement);
if (dependency == null) {
continue;
}
if (isSelected(changedElement)) {
validateSelected(dependency);
} else {
validateUnSelected(dependency);
}
}
if (debug) {
System.out
.println("There are (" + invalidNodesMap.size() + ") errors"); //$NON-NLS-1$ //$NON-NLS-2$
}
}
/**
* Validates the element dependency when the element is selected. When the
* element is selected, we need to do the following: 1. Check error for
* references 2. Remove error for dependents associated with element 3.
* Update parents: if the selection is valid, remove all errors from parents
* associated with this element if the selection is invalid, set error to
* all parents.
*
* @param dependency
* ElementDependency
*/
private void validateSelected(ElementDependency dependency) {
Object changedElement = dependency.getElement();
// Since this element is selected, remove all previous errors.
removeError(changedElement);
if (changedElement instanceof MethodLibrary) {
return;
}
// Since this element is selected, remove all dependency errors in the
// dependent elements.
List dependents = dependency.getDependents();
if (dependents != null && dependents.size() > 0) {
Object element;
for (Iterator itr = dependents.iterator(); itr.hasNext();) {
element = itr.next();
ElementDependencyError error = getError(element, false);
if (error != null) {
error.removeError(changedElement);
if (error.size() == 0) {
removeError(element);
// Clear the parent error introduced by this element.
updateParentsForErrors((EObject) element);
}
}
}
}
// If an element is checked, check the element it depends on,
// which should be checked as well.
List refs = dependency.getReferences();
if (refs != null && refs.size() > 0) {
PackageReference ref;
Object element;
for (Iterator itr = refs.iterator(); itr.hasNext();) {
ref = (PackageReference) itr.next();
element = ref.getRefElement();
if (element instanceof MethodLibrary) {
continue;
}
// Don't warn on optional inputs not being
// present, so added the canIgnore() method
if (!isSelected(element) && !canIgnore(ref)) {
String message;
int errorType = 0;
if (ref.hasBaseReference()) {
errorType = ErrorInfo.ERROR;
message = LibraryResources.configClosureWarning_msg2;
} else {
errorType = ErrorInfo.WARNING;
message = LibraryResources.configClosureWarning_msg3;
}
ElementDependencyError error = getError(changedElement,
true);
error.addError(new ErrorInfo(errorType, message,
changedElement, element, ErrorInfo.REFERENCE_TO));
} else {
ElementDependencyError error = getError(changedElement,
false);
if (error != null) {
error.removeError(element);
}
}
}
}
// Finally, update the parents.
updateParentsForErrors((EObject) changedElement);
}
/**
* Validates the ElementDependency when the element is unselected. When the
* element is unselected, we need to do the following: 1. check error for
* dependencts 2. remove error for references associated with element, in
* case of any added when the element was check 3. update parents: if the
* selection is valid, remove all errors from parents associated with this
* element if the selection is invalid, set error to all parents.
*
* @param dependency
* A <code>ElementDependency</code> object.
*/
private void validateUnSelected(ElementDependency dependency) {
Object changedElement = dependency.getElement();
// Since this element is un-selected, remove all previous errors.
removeError(changedElement);
if (changedElement instanceof MethodLibrary) {
return;
}
// Since this element is un-selected, remove all errors in the
// referenced elements.
List refs = dependency.getReferences();
if (refs != null && refs.size() > 0) {
ElementReference ref;
Object element;
for (Iterator itr = refs.iterator(); itr.hasNext();) {
ref = (ElementReference) itr.next();
element = ref.getRefElement();
ElementDependencyError error = getError(element, false);
if (error != null) {
error.removeError(changedElement);
if (error.size() == 0) {
removeError(element);
// Clear the parent error introduced by this element.
updateParentsForErrors((EObject) element);
}
}
}
}
// If an element is unchecked, check the dependent elements.
// If there are check elements depending on it, the element can't be
// unchecked.
List dependents = dependency.getDependents();
if (dependents != null && dependents.size() > 0) {
Object element;
for (Iterator itr = dependents.iterator(); itr.hasNext();) {
element = itr.next();
if (element instanceof MethodLibrary) {
continue;
}
if (isSelected(element)) {
// Determine the type of dependency.
ElementDependency childDep = dependencyManager
.getDependency((MethodElement) element);
validateSelected(childDep);
} else {
removeError(changedElement);
}
}
}
// finally, update the parents
updateParentsForErrors((EObject) changedElement);
}
private void updateParentError(EObject parent, EObject element,
int errorType) {
if (parent == null || (parent instanceof MethodLibrary)) {
return;
}
if ((parent instanceof MethodPackage)
&& ConfigurationHelper.isGlobalPackage((MethodPackage) parent)) {
updateParentError(parent.eContainer(), element, errorType);
return;
}
// Remove the error associated with this element from all parents.
ElementDependencyError error = getError(parent, false);
if (error != null && error.size() > 0) {
error.removeError(element);
}
if (errorType != ErrorInfo.NONE) {
// Propegate the error to all parents.
error = getError(parent, true);
String message = LibraryResources.configClosureWarning_msg1;
error.addError(new ErrorInfo(errorType, message, parent, element,
ErrorInfo.NONE));
} else if ((error != null) && (error.size() == 0)) {
removeError(parent);
}
updateParentError(parent.eContainer(), element, errorType);
}
private void updateParentsForErrors(EObject element) {
int errorType = ErrorInfo.NONE;
ElementDependencyError error = getError(element);
if (error != null && error.size() > 0) {
if (error.isError() || error.isChildError()) {
errorType = ErrorInfo.CHILD_ERROR;
} else if (error.isWarning() || error.isChildWarning()) {
errorType = ErrorInfo.CHILD_WARNING;
}
}
updateParentError(element.eContainer(), element, errorType);
}
/**
* accept the cutrrent selection and ignore any warning message. make the
* configuration.
*
*/
public void makeClosure() {
// If no error. update the method configuration.
while (hasError()) {
fixProblems(true);
}
}
/**
* fix all error(s) and warnign(s)
*
*/
public void fixProblems() {
// If no error, update the method configuration.
while (hasProblem()) {
fixProblems(false);
}
}
private void fixProblems(boolean errorOnly) {
// Note: make closure will select elements as needed.
// so we need to make a copy of the current selcted ones
// in order to trace the status
List currentSelected = new ArrayList(selected);
// beginUpdate();
// list of errorInfo objects
List errors = getAllErrors();
if (errors.size() > 0) {
invalidNodesMap.clear();
ErrorInfo error;
EObject ownerElement, causeElement;
boolean ownerSelected, causeSelected;
for (Iterator it = errors.iterator(); it.hasNext();) {
error = (ErrorInfo) it.next();
ownerElement = (EObject) error.getOwnerElement();
causeElement = (EObject) error.getCauseElement();
addChanged(ownerElement);
addChanged(causeElement);
if (error.isChildError() || error.isChildWarning()) {
continue;
}
if (error.isWarning() && errorOnly) {
continue;
}
ownerSelected = currentSelected.contains(ownerElement);
causeSelected = currentSelected.contains(causeElement);
// If the owner element is not selected
// the error is caused by un-selecting this element,
// select it will fix the error
// If the owner element is selected, the error is caused by
// un-selected references.
// select those cause elements will fix the error
if (!ownerSelected) {
selectErrorElement(ownerElement);
} else if (!causeSelected) {
selectErrorElement(causeElement);
}
}
}
endUpdate();
}
private void selectErrorElement(EObject element) {
// The selection is based on a package level. When a non-package element
// is selected, all it's non-package siblings must be selected as well.
if (LibraryUtil.selectable(element)) {
add(element, true);
}
}
/**
* update the method configuration in the library with the current selections
*/
public void saveMethodConfiguration() {
List plugins = config.getMethodPluginSelection();
List packages = config.getMethodPackageSelection();
plugins.clear();
packages.clear();
EObject element;
for (Iterator it = selected.iterator(); it.hasNext();) {
element = (EObject) it.next();
if (element instanceof MethodPlugin) {
if (!plugins.contains(element)) {
plugins.add(element);
}
} else if ((element instanceof MethodPackage)
&& !ConfigurationHelper
.isGlobalPackage((MethodPackage) element)) {
if (!packages.contains(element)) {
packages.add(element);
}
}
}
}
/**
* Packages the library based on the selection.
* <p>
* Note: This will change the current library. Before calling this method, a
* copy of the current library should be created with the following steps:
* 1. Create a new <code>ConfigurationManager</code> with a copy of the
* original library, 2. Rebuild the dependency, 3. Create a
* <code>ConfigurationClosure</code> with the current configuration.
*
* @return A <code>MethodLibrary</code>.
*/
public MethodLibrary packageLibrary(boolean removeBrokenReferences) {
processSelection(library, removeBrokenReferences);
// Remove the configurations except for the current one.
List configs = library.getPredefinedConfigurations();
configs.clear();
configs.add(config);
return library;
}
/**
* process the selected package by removeing all unselected elements and any
* missing references
*
* @param element
*/
private void processSelection(EObject element,
boolean removeBrokenReferences) {
if (removeBrokenReferences) {
// Iterator the references and remove broken references.
EList references = element.eCrossReferences();
if (references != null) {
for (Iterator it = new ArrayList(references).iterator(); it
.hasNext();) {
EObject ref = (EObject) it.next();
EObject pkgRef = LibraryUtil.getSelectable(ref);
if (pkgRef != null && !isSelected(pkgRef)) {
removeReference(element, ref);
}
}
}
}
EList elements = element.eContents();
if (elements != null) {
for (Iterator it = new ArrayList(elements).iterator(); it.hasNext();) {
EObject child = (EObject) it.next();
// If the child element is selectable but it is not in the
// configuration, remove it.
if (LibraryUtil.selectable(child) && !isSelected(child)) {
EcoreUtil.remove(child);
} else {
processSelection(child, removeBrokenReferences);
}
}
}
}
private void removeReference(EObject ownerElement, EObject refElement) {
AdapterFactoryContentProvider provider = configManager
.getContentProvider();
IPropertySource ps = provider.getPropertySource(ownerElement);
IPropertyDescriptor[] pds = ps.getPropertyDescriptors();
if (pds != null && pds.length > 0) {
for (int i = 0; i < pds.length; i++) {
IPropertyDescriptor descriptor = (IPropertyDescriptor) pds[i];
Object id = descriptor.getId();
Object value = ps.getPropertyValue(id);
// Check whether the value needs to be converted to an editable
// value.
IPropertySource source = provider.getPropertySource(value);
if (source != null) {
value = source.getEditableValue();
}
if (value instanceof EList) {
EList refList = (EList) value;
if (refList.contains(refElement)) {
if (debug) {
System.out
.println("Reference [" + LibraryUtil.getName(refElement) //$NON-NLS-1$
+ "] removed from [" //$NON-NLS-1$
+ LibraryUtil.getName(ownerElement)
+ "]'s reference list"); //$NON-NLS-1$
}
refList.remove(refElement);
ps.setPropertyValue(id, refList);
}
} else if (value instanceof MethodElement) {
if (debug) {
System.out
.println("Reference [" + LibraryUtil.getName(refElement) //$NON-NLS-1$
+ "] removed from [" //$NON-NLS-1$
+ LibraryUtil.getName(ownerElement)
+ "]"); //$NON-NLS-1$
}
ps.setPropertyValue(id, null);
}
}
}
}
/**
* Disposes resources allocated by this closure.
*/
public void dispose() {
configManager = null;
config = null;
library = null;
dependencyManager = null;
selected.clear();
changedNodes.clear();
invalidNodesMap.clear();
}
private boolean canIgnore(PackageReference pkgRef) {
return pkgRef.canIgnore();
}
}