package gov.nasa.jpl.mbee.mdk.constraint; import com.nomagic.uml2.ext.jmi.helpers.StereotypesHelper; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Comment; import com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Element; import gov.nasa.jpl.mbee.mdk.api.incubating.convert.Converters; import gov.nasa.jpl.mbee.mdk.docgen.DocGenProfile; import gov.nasa.jpl.mbee.mdk.docgen.DocGenUtils; import gov.nasa.jpl.mbee.mdk.util.*; import gov.nasa.jpl.mbee.mdk.ocl.OclEvaluator; import gov.nasa.jpl.mbee.mdk.util.Pair; import java.util.*; /** * A constraint in the context of a model defined by elements that act as * constraints and elements that are constrained. */ public class BasicConstraint implements Constraint { public enum Type { UML, STATIC, DYNAMIC, CONSTRAINT_ST, VIEWPOINT_CONSTRAINT_ST, ANY } LinkedHashSet<Element> constrainingElements; private LinkedHashSet<Object> constrainedObjects; Element violatedConstraintElement = null; Element violatedConstrainedElement = null; protected Boolean isConsistent = null; protected String errorMessage = null; protected boolean reported = false; public BasicConstraint(Object constraint, Object constrained) { addConstrainingObject(constraint); addConstrainedObject(constrained); } public BasicConstraint(Object constraint, Collection<Object> constrained) { addConstrainingObject(constraint); addConstrainedObjects(constrained); } public static boolean iterateViewpointConstrraint(Element vpConstraint) { Boolean iterate = (Boolean) GeneratorUtils.getObjectProperty(vpConstraint, DocGenProfile.viewpointConstraintStereotype, "iterate", true); // Boolean iterate = getBooleanPropertyValue( vpConstraint, // DocGenProfile.expressionChoosable, "iterate" ); boolean result = !Boolean.FALSE.equals(iterate); return result; } public static boolean reportedViewpointConstrraint(Element vpConstraint) { Boolean report = (Boolean) GeneratorUtils.getObjectProperty(vpConstraint, DocGenProfile.viewpointConstraintStereotype, "validationReport", false); boolean result = Boolean.TRUE.equals(report); return result; } /** * Determines whether the input Constraint, constraint element, or all * constraints in a collection are of the specified type. This establishes a * policy for how the different kinds of constraints can be used. * * @param constraint * @param type * @return true iff the constraint is of the specified type */ public static boolean constraintIsType(Object constraint, Type type) { if (constraint instanceof Constraint) { return constraintIsType(((Constraint) constraint).getConstrainingElements(), type); } if (constraint instanceof Collection) { for (Object c : (Collection<?>) constraint) { if (constraintIsType(c, type)) { return true; } } } if (constraint instanceof Element) { Element elem = (Element) constraint; if (!elementIsConstraint(elem)) { return false; } switch (type) { case ANY: return true; case UML: return elementIsUmlConstraint(elem); case CONSTRAINT_ST: return elementIsDocGenConstraint(elem); case VIEWPOINT_CONSTRAINT_ST: case DYNAMIC: return elementIsViewpointConstraint(elem); case STATIC: return !elementIsViewpointConstraint(elem); default: return false; } } return false; } @Override public Set<Object> getConstrainedObjects() { if (constrainedObjects == null) { constrainedObjects = new LinkedHashSet<Object>(); } return constrainedObjects; } @Override public Set<Element> getConstrainingElements() { return getConstrainingElements(Type.ANY); } public Set<Element> getConstrainingElements(Type type) { if (constrainingElements == null) { constrainingElements = new LinkedHashSet<Element>(); } if (type == Type.ANY) { return constrainingElements; } Set<Element> filtered = new LinkedHashSet<Element>(); for (Element c : constrainingElements) { if (constraintIsType(c, type)) { filtered.add(c); } } return filtered; } @Override public void addConstrainedObjects(Collection<Object> objects) { getConstrainedObjects().addAll(objects); } @Override public void addConstrainedObject(Object obj) { getConstrainedObjects().add(obj); } public void addConstrainingObject(Object obj) { addConstrainingObject(obj, null); } public void addConstrainingObject(Object obj, Set<Object> seen) { Pair<Boolean, Set<Object>> p = Utils2.seen(obj, true, seen); if (p.getKey()) { return; } seen = p.getValue(); if (obj instanceof Element) { addConstrainingElement((Element) obj); } if (obj instanceof Collection) { for (Object o : (Collection<?>) obj) { addConstrainingObject(o); } } } @Override public void addConstrainingElement(Element constrainingElement) { if (constrainingElements == null) { constrainingElements = new LinkedHashSet<Element>(); } constrainingElements.add(constrainingElement); if (!reported && elementIsViewpointConstraint(constrainingElement)) { setReported(reportedViewpointConstrraint(constrainingElement)); } } @Override public void addConstrainingElements(Collection<Element> elements) { for (Element e : elements) { addConstrainingElement(e); } } @Override public String getExpression() { StringBuffer sb = new StringBuffer(); boolean first = true; boolean multiple = getConstrainingElements().size() > 1; for (Element e : getConstrainingElements()) { if (first) { first = false; } else { sb.append(" and "); } if (multiple) { sb.append("("); } String expr = getExpression(e); if (!Utils2.isNullOrEmpty(expr)) { sb.append(expr); } else if (!Utils2.isNullOrEmpty(e.getHumanName())) { sb.append(e.getHumanName()); } else { sb.append(e.getHumanType()); } if (multiple) { sb.append(")"); } } return sb.toString(); } @Override public Boolean evaluate() { return evaluate(true); } public Boolean evaluate(boolean complainIfFails) { violatedConstraintElement = null; violatedConstrainedElement = null; // try evaluating constraint on elements as a collection Boolean satisfied = false; try { satisfied = evaluate(getConstrainedObjects(), false); } catch (Throwable e) { Debug.error(true, true, "Didn't work on elements as a collection:\n"); e.printStackTrace(); } if (Boolean.TRUE.equals(satisfied)) { return satisfied; } Boolean oldSatisfied = satisfied; boolean oldIsConsistent = isConsistent(); boolean newIsConsistent = !Utils2.isNullOrEmpty(getConstrainedObjects()); String oldErrorMessage = errorMessage; if (newIsConsistent) { satisfied = true; } // try evaluating targets of a collection separately as a // conjunction of constraints boolean gotNull = false; for (Object target : getConstrainedObjects()) { satisfied = evaluate(target, false); if (!isConsistent()) { newIsConsistent = false; // if ( !Utils2.isNullOrEmpty( errorMessage ) ) { // newErrorMsg = // newErrorMsg + ( newErrorMsg.length() > 0 // ? "" + Character.LINE_SEPARATOR // : "" ) + errorMessage; // } } if (satisfied == null) { gotNull = true; } else if (satisfied.equals(Boolean.FALSE)) { // isConsistent = newIsConsistent; // // errorMessage = newErrorMsg; // if ( !isConsistent() && !Utils2.isNullOrEmpty( errorMessage ) // ) { // Debug.error( complainIfFails, false, errorMessage ); // } // return false; break; } } isConsistent = newIsConsistent || oldIsConsistent; if (!isConsistent()) { errorMessage = oldErrorMessage; if (!Utils2.isNullOrEmpty(errorMessage)) { Debug.error(complainIfFails, false, errorMessage); } } // errorMessage = newErrorMsg; if (satisfied != null) { return satisfied; } if (oldIsConsistent) { satisfied = oldSatisfied; } return gotNull ? null : true; } protected Boolean evaluate(Object constrainedObject) { return evaluate(constrainedObject, true); } protected Boolean evaluate(Object constrainedObject, boolean complainIfFails) { boolean gotNull = false; isConsistent = true; errorMessage = null; for (Element constraint : getConstrainingElements()) { Object res = null; try { res = OclEvaluator.evaluateQuery(constrainedObject, constraint); OclEvaluator evaluator = OclEvaluator.instance; if (isConsistent) { isConsistent = evaluator.isValid(); } } catch (Exception e) { this.errorMessage = e.getLocalizedMessage() + " for OCL query \"" + getExpression(constraint) + "\" on " + Utils.toStringNameAndType(constrainedObject, true, true); try { Debug.error(complainIfFails, false, this.errorMessage); } catch (Exception ex) { System.err.println(this.errorMessage); } isConsistent = false; } if (res == null) { gotNull = true; } else if (!Utils.isTrue(res, false)) { violatedConstraintElement = constraint; if (constrainedObject instanceof Element) { violatedConstrainedElement = (Element) constrainedObject; } return false; } } return gotNull ? null : true; } @Override public Element getViolatedConstraintElement() { if (violatedConstraintElement == null) { evaluate(); } return violatedConstraintElement; } public Element getViolatedConstrainedElement() { if (violatedConstrainedElement == null) { evaluate(); } return violatedConstrainedElement; } public static String getExpression(Object constraint) { if (constraint instanceof Constraint) { return ((Constraint) constraint).getExpression(); } String expr = null; if (constraint instanceof Element) { Element e = (Element) constraint; if (elementIsUmlConstraint(e)) { expr = DocGenUtils.fixString(asUmlConstraint(e).getSpecification()); } else if (GeneratorUtils.hasStereotypeByString(e, DocGenProfile.constraintStereotype, true)) { Object v = GeneratorUtils.getObjectProperty(e, DocGenProfile.constraintStereotype, "expression", null); expr = v.toString(); } } if (Utils2.isNullOrEmpty(expr)) { expr = OclEvaluator.queryObjectToStringExpression(constraint); } return expr; } /** * Create a BasicConstraint on one of two Elements or Collections. * * @param constraintElement the model element representing the constraint * @param constrained1 the first candidate to be constrained * @param constrained2 the second candidate to be constrained * @return a BasicConstraint on the first candidate if the evaluation works * or the evaluation does not work with the second candidate; * otherwise return a BasicConstraint on the second candidate. */ public static BasicConstraint makeConstraintFromAlternativeContexts(Object constraintElement, Object... candidateContexts) { BasicConstraint c = null; if (!Utils2.isNullOrEmpty(candidateContexts)) { BasicConstraint firstNull = null; Boolean result = null; for (Object constrained : candidateContexts) { c = new BasicConstraint(constraintElement, constrained); result = c.evaluate(false); if (result != null) { break; } else if (firstNull == null || (Utils2.isNullOrEmpty(firstNull.getConstrainedObjects()) && !Utils2 .isNullOrEmpty(c.getConstrainedObjects()))) { firstNull = c; } } if (result == null) { c = firstNull; } } if (c == null) { Object constrained = Utils2.isNullOrEmpty(candidateContexts) ? null : candidateContexts[0]; c = new BasicConstraint(constraintElement, constrained); } return c; } /** * Expression evaluation expects a list of targets. Make sure the targets * are in a list and not buried in an extra list. * * @param targets * @return */ public static Object fixTargets(Object targets) {// , Element vpConstraint ) // { if (targets == null) { return null; } // if ( vpConstraint == null ) return targets; Object constrained = targets; // See if the constraint is supposed to be iteratively applied to each // in a list or to the list as a whole. // // if ( iterateViewpointConstrraint( vpConstraint ) ) { // If iterating, be sure that list isn't buried in another list. if (constrained instanceof Collection) { Collection<?> coll = (Collection<?>) constrained; if (!coll.isEmpty()) { Object first = coll.iterator().next(); if (first instanceof Collection && coll.size() == 1) { constrained = first; } } } // // } else { // Expecting targets to be processed as a single list if (constrained instanceof Element) { constrained = Utils2.newList(constrained); } // if ( constrained instanceof Collection ) { // Collection< ? > coll = (Collection<?>)constrained; // if ( !coll.isEmpty() ) { // Object first = coll.iterator().next(); // if ( first instanceof Element ) { // constrained = Utils2.newList(constrained); // } // } // } // // } return constrained; } public static BasicConstraint makeConstraint(Element constraintElement) { if (!elementIsConstraint(constraintElement)) { return null; } List<Object> constrained = getConstrainedObjectsFromConstraintElement(constraintElement); BasicConstraint c = new BasicConstraint(constraintElement, constrained); return c; } public static Boolean evaluateAgainst(Object constraint, Object constrained, List<Object> targets) { BasicConstraint c = makeConstraintFromAlternativeContexts(constraint, targets, constrained); Boolean result = c.evaluate(); return result; } @Override public String toString() { return toShortString(); } public String toShortString() { StringBuffer sb = new StringBuffer(); sb.append("Constraint:\"" + this.getExpression() + "\" on " + Utils.toStringNameAndType(this.constrainedObjects, true, true)); return sb.toString(); } protected static String toString(Object o, boolean showElementId) { if (o instanceof Element) { return toString((Element) o, showElementId); } return MoreToString.Helper.toString(o); } protected static String toString(Element e, boolean showElementId) { return Utils.getName(e) + (showElementId ? "[" + Converters.getElementToIdConverter().apply(e) + "]" : ""); } protected static String toString(Collection<? extends Object> coll, boolean showElementId) { return toString(coll, Integer.MAX_VALUE, showElementId); } protected static String toString(Collection<? extends Object> coll, int maxNumber, boolean showElementId) { if (maxNumber <= 0 || Utils2.isNullOrEmpty(coll)) { return ""; } if (coll.size() == 1) { return toString(coll.iterator().next(), showElementId); } StringBuffer sb = new StringBuffer(); sb.append("( "); int ct = 0; for (Object o : coll) { String oStr = toString(o, showElementId); if (Utils2.isNullOrEmpty(oStr)) { continue; } if (ct > 0) { sb.append(", "); } sb.append(oStr); ct++; if (ct >= maxNumber) { break; } } if (ct < coll.size()) { if (ct > 0) { sb.append(", "); } sb.append("and " + (coll.size() - ct) + " more"); } sb.append(" )"); return sb.toString(); } public String toString(int maxNumber, boolean showElementIds) { StringBuffer sb = new StringBuffer(); Element constrainingElement = (Utils2.isNullOrEmpty(getConstrainingElements()) ? null : getConstrainingElements().iterator().next()); sb.append("Constraint " + toString(constrainingElement, showElementIds) + " with expression, \"" + this.getExpression() + "\" on " + toString(this.constrainedObjects, maxNumber, showElementIds)); return sb.toString(); } public String toStringViolated(int maxNumberOfViolatingElementsToShow, boolean showElementIds) { Element violatedElement = this.getViolatedConstraintElement(); Set<Object> target = this.getConstrainedObjects(); StringBuffer comment = new StringBuffer(); comment.append("constraint " + toString(violatedElement, showElementIds)); comment.append(" with expression, \"" + getExpression() + "\""); comment.append(" is violated"); if (maxNumberOfViolatingElementsToShow > 0 && !Utils2.isNullOrEmpty(target)) { comment.append(" for " + toString(target, maxNumberOfViolatingElementsToShow, showElementIds)); } return comment.toString(); } public static List<Element> getComments(Element source) { List<Element> results = new ArrayList<Element>(); results.addAll(source.get_commentOfAnnotatedElement()); if (results.size() > 0) { Debug.out(""); } return results; } /** * Get the elements that constrain this element. * * @param constrainedObject * @return a list of constraint elements that constrain the * constrainedObject */ public static List<Element> getConstraintElements(Object constrainedObject) { return getConstraintElements(constrainedObject, Type.ANY); } public static List<Element> getConstraintElements(Object constrainedObject, Type type) { LinkedHashSet<Element> constraintElements = new LinkedHashSet<Element>(); if (constrainedObject instanceof Element) { Element constrainedElement = ((Element) constrainedObject); // Is the element stereotyped as <<Constraint>> or a UML Constraint? if (elementIsConstraintOnItself(constrainedElement)) { if (constraintIsType(constrainedElement, type)) { constraintElements.add(constrainedElement); } } // Get the constraints MD finds Collection<com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Constraint> constrs = constrainedElement .get_constraintOfConstrainedElement(); if (constrs != null) { for (Element c : constrs) { if (constraintIsType(c, type)) { constraintElements.add(c); } } } // Add any comment constraints annotating this element for (Element comment : BasicConstraint.getComments(constrainedElement)) { if (elementIsDocGenConstraint(comment)) { if (constraintIsType(comment, type)) { constraintElements.add(comment); } } } } // Collect constraints from each item in the object as a Collection // TODO -- infinite loop if element contains itself! Use Utils2.seen() if (constrainedObject instanceof Collection) { for (Object o : (Collection<?>) constrainedObject) { constraintElements.addAll(getConstraintElements(o, type)); } } return Utils2.toList(constraintElements); } /** * Get the elements that the input element constrains, if a constraint. * * @param elem * @return */ public static List<Object> getConstrainedObjectsFromConstraintElement(Element elem) { Set<Object> constrained = new LinkedHashSet<Object>(); if (elementIsDocGenConstraint(elem)) { if (elem instanceof Comment) { Collection<Element> annotatedElems = ((Comment) elem).getAnnotatedElement(); if (Utils2.isNullOrEmpty(annotatedElems)) { constrained.add(elem); } else { constrained.addAll(annotatedElems); } } } if (elementIsUmlConstraint(elem)) { constrained.addAll(asUmlConstraint(elem).getConstrainedElement()); } return Utils2.toList(constrained); } public static com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Constraint asUmlConstraint(Element elem) { if (elementIsUmlConstraint(elem)) { return (com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Constraint) elem; } return null; } public static boolean elementIsUmlConstraint(Element elem) { return elem instanceof com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Constraint; } public static boolean elementIsDocGenConstraint(Element elem) { if (elem == null) { return false; } return StereotypesHelper.hasStereotypeOrDerived(elem, DocGenProfile.constraintStereotype); } public static boolean elementIsViewpointConstraint(Element elem) { if (elem == null) { return false; } return StereotypesHelper.hasStereotypeOrDerived(elem, DocGenProfile.viewpointConstraintStereotype); } public static boolean elementIsConstraint(Element elem) { return (elementIsUmlConstraint(elem) || elementIsDocGenConstraint(elem) || elementIsViewpointConstraint(elem)); } /** * Determines whether the element is a constraint on itself * * @param elem * @return true iff it is not a constraint or constrains only other elements */ public static boolean elementIsConstraintOnItself(Element elem) { // If a <<Constraint>>, then it constrains self if it is not a Comment, // or it is included in the elements it annotates (as a Comment). if (elementIsDocGenConstraint(elem)) { if (!(elem instanceof Comment)) { return true; } Comment comment = (Comment) elem; return !Utils2.isNullOrEmpty(comment.getAnnotatedElement()) && comment.getAnnotatedElement().contains(elem); } if (elementIsUmlConstraint(elem)) { com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Constraint umlConstr = (com.nomagic.uml2.ext.magicdraw.classes.mdkernel.Constraint) elem; List<Element> constrained = umlConstr.getConstrainedElement(); return constrained.contains(elem); } return elementIsViewpointConstraint(elem); } /** * @param constrainedObject the object for which constraints are sought * @return constraints on the constrainedObject, only including the * constrainedObject if it is a constraint on itself */ public static List<Constraint> getConstraints(Object constrainedObject) { return getConstraints(constrainedObject, Type.ANY); } public static List<Constraint> getConstraints(Object constrainedObject, Type type) { List<Constraint> constraints = new ArrayList<Constraint>(); List<Element> constraintElements = getConstraintElements(constrainedObject, type); for (Element constraint : constraintElements) { Constraint c = BasicConstraint.makeConstraint(constraint);// , // constrainedObject // ); if (c != null) { constraints.add(c); } } return constraints; } @Override public boolean isConsistent() { if (isConsistent == null) { evaluate(); } return isConsistent; } public String getErrorMessage() { return errorMessage; } public void setErrorMessage(String errorMessage) { this.errorMessage = errorMessage; } @Override public boolean isReported() { return false; } public void setReported(boolean b) { reported = b; } }