/** * */ package org.feature.multi.perspective.model.editor.util; import java.util.ArrayList; import java.util.Collection; import java.util.HashSet; import java.util.LinkedList; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import org.eclipse.core.resources.IFile; import org.eclipse.core.resources.IWorkspace; import org.eclipse.core.resources.ResourcesPlugin; import org.eclipse.core.runtime.IPath; import org.eclipse.core.runtime.Path; import org.eclipse.draw2d.ColorConstants; import org.eclipse.emf.common.ui.dialogs.WorkspaceResourceDialog; import org.eclipse.emf.common.util.EList; import org.eclipse.emf.common.util.URI; import org.eclipse.emf.ecore.resource.Resource; import org.eclipse.emf.ecore.resource.ResourceSet; import org.eclipse.emf.ecore.util.EcoreUtil; import org.eclipse.swt.SWT; import org.eclipse.swt.graphics.Color; import org.eclipse.swt.widgets.FileDialog; import org.eclipse.swt.widgets.Shell; import org.eclipse.zest.core.widgets.GraphConnection; import org.eclipse.zest.core.widgets.GraphNode; import org.feature.model.utilities.FeatureModelInit; import org.feature.model.utilities.ResourceUtil; import org.js.model.feature.AttributeConstraint; import org.js.model.feature.Constraint; import org.js.model.feature.Feature; import org.js.model.feature.FeatureModel; import org.js.model.feature.Group; import org.js.model.feature.Imply; import org.js.model.feature.csp.FeatureModelAnalyzer; import org.js.model.feature.edit.FeatureModelHelper; /** * Utility class for the multi-perspective editor. * * @author Tim Winkelmann * */ public class Util { public static final Color attachedGraphNodeBackgroundColor = ColorConstants.green; public static final Color unattachedGraphNodeBackgroundColor = new Color(null, 216, 228, 248); public static final Color attachedGraphConnectionLineColor = ColorConstants.green; public static final Color unattachedGraphConnectionLineColor = new Color(null, 192, 192, 192); public static List<GraphNode> attachedGraphNodes = new LinkedList<GraphNode>(); public static List<GraphConnection> highlightedGraphConnections = new LinkedList<GraphConnection>(); public static Logger log = Logger.getLogger(Util.class); /** * creates a view from the {@link FeatureModel} * * @param featureModel the original {@link FeatureModel} * @param features which are part of the view. * @return a view from a {@link FeatureModel} */ public static FeatureModel createFeatureModel(FeatureModel featureModel, Set<Feature> features, Flag canBeConsistent) { FeatureModelHelper helper = new FeatureModelHelper(featureModel); if (helper.getAllFeatures().size() == features.size()) { return featureModel; } URI uri = featureModel.eResource().getURI().trimFileExtension().trimFragment(); ResourceSet resourceSet = featureModel.eResource().getResourceSet(); String uriString = uri.toString() + System.currentTimeMillis() + features.hashCode(); // log.debug("FeaturemodelURI: " + uriString); uri = URI.createURI(uriString); uri = uri.appendFileExtension("feature"); Resource resource = resourceSet.createResource(uri); FeatureModel view = EcoreUtil.copy(featureModel); traverseFeatureModelAndRemoveFeatures(view.getRoot(), features); FeatureModelHelper viewHelper = new FeatureModelHelper(view); if (viewHelper.getAllFeatures().size() == features.size()) { removeUnusedConstraints(view, featureModel, canBeConsistent); resource.getContents().add(view); } else { view = null; } return view; } /** * removes condition where the condition is a tautology, due to missing features and simple implications and * exclusions. * * @param view a subset of the {@link FeatureModel}. * @param featureModel the full {@link FeatureModel}. * @param canBeConsistent if an implies constraint can never be fulfilled. */ private static void removeUnusedConstraints(FeatureModel view, FeatureModel featureModel, Flag canBeConsistent) { // log.debug("view features:\t" + view.getAllFeatures().size()); // log.debug("featureModel features:\t" + // featureModel.getAllFeatures().size()); FeatureModelHelper viewHelper = new FeatureModelHelper(view); Set<Feature> allFeatures = viewHelper.getAllFeatures(); EList<Constraint> constraints = view.getConstraints(); Set<Constraint> constraintsToRemove = new HashSet<Constraint>(); boolean isConsistent = false; for (Constraint constraint : constraints) { Set<Feature> featuresFromTerm = FeatureModelHelper.getConstrainedFeatures(constraint); boolean oneFeatureMissing = false; for (Feature feature : featuresFromTerm) { if (!contains(feature, allFeatures)) { oneFeatureMissing = true; break; } } if (oneFeatureMissing){ if (constraint instanceof Imply){ Imply implyConstraint = (Imply) constraint; Feature rightOperand = implyConstraint.getRightOperand(); Feature leftOperand = implyConstraint.getLeftOperand(); boolean isRightContained = contains(rightOperand, viewHelper.getAllFeatures()); boolean isLeftContained = contains(leftOperand, viewHelper.getAllFeatures()); // if only the left feature of an imply constrain is contained without the right, the model cannot become consistent if (isLeftContained && !isRightContained) { isConsistent = true; } } constraintsToRemove.add(constraint); } } canBeConsistent.setFlagged(isConsistent); view.getConstraints().removeAll(constraintsToRemove); // log.debug("Constraints removed: " + constraintsToRemove.size()); } /** * removes all {@link Feature} from the {@link FeatureModel} which are not in the {@link Set}. * * @param parentFeature a parent {@link Feature}. First call with root {@link Feature} . * @param features {@link Feature} which are part of the view. */ private static void traverseFeatureModelAndRemoveFeatures(Feature parentFeature, Set<Feature> features) { if (contains(parentFeature, features)) { EList<Group> groups = parentFeature.getGroups(); Set<Group> groupsToRemove = new HashSet<Group>(); for (Group group : groups) { EList<Feature> childFeatures = group.getChildFeatures(); Set<Feature> featuresToRemove = new HashSet<Feature>(); for (Feature feature : childFeatures) { if (contains(feature, features)) { traverseFeatureModelAndRemoveFeatures(feature, features); } else {// Remove feature featuresToRemove.add(feature); } } for (Feature feature : featuresToRemove) { int numberChildren = group.getChildFeatures().size(); group.getChildFeatures().remove(feature); int max = group.getMaxCardinality(); int min = group.getMinCardinality(); //reduce max and min cardinality accordingly if (max == numberChildren){ group.setMaxCardinality(numberChildren-1); } if (min == numberChildren){ group.setMinCardinality(numberChildren-1); } } if (group.getChildFeatures().isEmpty()) { groupsToRemove.add(group); } } for (Group group : groupsToRemove) { parentFeature.getGroups().remove(group); } } } /** * checks if the set contains the {@link Feature}. Check is made by name. * * @param feature the {@link Feature}. * @param features the set of {@link Feature}. * @return true if the name of the {@link Feature} is equal to one in the set. */ private static boolean contains(Feature feature, Collection<Feature> features) { if (feature != null && feature.getName() != null) { for (Feature feature2 : features) { if (feature2 != null && feature2.getId() != null) { if (feature.getId().equals(feature2.getId())) { return true; } } } } return false; } /** * checks if the view is able to create a variant. * * @param view the filtered {@link FeatureModel} * @return true if there is at least one variant. */ public static boolean isConsistent(FeatureModel view) { boolean result = false; if (view != null) { FeatureModelAnalyzer featureModelAnalyzer = new FeatureModelAnalyzer(view); result = featureModelAnalyzer.isSatisfiable(); } return result; } /** * checks the hierarchy of the features, the cardininalities and the constraint. * * @return true is it is consistent. */ public static boolean isConsistent(Set<Feature> features) { return checkHierarchy(features) & checkCardinalities(features) & checkConstraints(features); } /** * Consistency requirements * - f1 requires f2 : if f1 included in featureSet, f2 must also be included * - f1 requires f2 : if f2 is included, f1 does not care * - f1 excludes f2 : does not care * -f1.a1 RelOp f2.a2 : if f1 included, f2 must also be included * -f1.a1 RelOp f2.a2 : if f2 included, f1 must also be included * * @param feature * @return */ public static boolean checkConstraints(Set<Feature> featureSet){ boolean result = true; FeatureModelHelper helper = null; for (Feature feature : featureSet) { if (helper == null){ FeatureModel fm = (FeatureModel) EcoreUtil.getRootContainer(feature); helper = new FeatureModelHelper(fm); } Set<Constraint> constraints = helper.getConstraintsRelatedToFeature(feature); for (Constraint constraint : constraints) { // only check Imply Constraints and Attribute Constraints if (constraint instanceof Imply) { Imply implyConstraint = (Imply) constraint; Feature leftOperand = implyConstraint.getLeftOperand(); if (EcoreUtil.equals(leftOperand, feature)){ Feature rightOperand = implyConstraint.getRightOperand(); result = (featureSet.contains(rightOperand)); if (result == false){ break; } } } else if (constraint instanceof AttributeConstraint) { AttributeConstraint attConstraint = (AttributeConstraint) constraint; Set<Feature> attributeFeatures = FeatureModelHelper.getConstrainedFeatures(attConstraint); result = (featureSet.containsAll(attributeFeatures)); if (result == false){ break; } } } if (result == false){ break; } } return result; } /** * checks the cardinalities from the features. * * @return true if a no cardinality is compromised */ private static boolean checkCardinalities(Set<Feature> features) { for (Feature feature : features) { EList<Group> groups = feature.getGroups(); for (Group fGroup : groups) { if (fGroup.getMinCardinality() > 0) { EList<Feature> childFeatures = fGroup.getChildFeatures(); int i = 0; for (Feature feature2 : childFeatures) { if (features.contains(feature2)) { i++; } } if (i < fGroup.getMinCardinality()) { // log.debug("Cardinalitycheck: " + feature.getName() + // " l:" + fGroup.getMinCardinality() + " l':" + // i); return false; } } } } return true; } /** * checks every ancestor from all features * * @return true if the hierarchy is complete for all features */ private static boolean checkHierarchy(Set<Feature> features) { for (Feature feature : features) { if (!checkAncestors(feature, features)) { return false; } } return true; } /** * checks the ancestors of a feature. * * @param feature the feature to be checked. * @return true if the hierarchy is complete */ private static boolean checkAncestors(Feature feature, Set<Feature> features) { if (feature.eContainer() == null) { return true;// root feature } Feature parentFeature = FeatureModelHelper.getParentFeature(feature); if (features.contains(parentFeature)) { return checkAncestors(parentFeature, features); } else { // log.debug("\t" + feature.getName() + " is missing his parent: " + // parentFeature.getName()); return false; } } /** * Opens a save Dialog. * * @param defaultFileName a default file name. * @param shell the shell is a needed parent for the dialog. * @return the filename and path. */ public static String save(String defaultFileName, Shell shell) { FileDialog fd = new FileDialog(shell, SWT.SAVE); // find shell fd.setFileName(defaultFileName); return fd.open(); } public static IFile openSaveDialog(Shell parent, String defaultFileName) { String title = "Save Perspective"; String message = "Please select a file to store the Perspective"; IPath suggestedPath = Path.fromOSString(defaultFileName); suggestedPath.append(defaultFileName); IFile file = WorkspaceResourceDialog.openNewFile(parent, title, message, suggestedPath, null); return file; } }