/* * Copyright (c) 2009 Borland Software Corporation * * 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: * Michael Golubev (Borland) */ package org.eclipse.uml2.diagram.sequence.anchor; import java.util.AbstractList; import java.util.ArrayList; import java.util.Arrays; import java.util.Collection; import java.util.Enumeration; import java.util.HashMap; import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; import org.eclipse.osgi.util.NLS; import org.eclipse.uml2.diagram.sequence.internal.layout.vertical.input.HorizontalConstraint; import org.eclipse.uml2.diagram.sequence.internal.layout.vertical.input.LifeLine; import org.eclipse.uml2.diagram.sequence.internal.layout.vertical.input.LifeLineElement; import org.eclipse.uml2.diagram.sequence.internal.layout.vertical.input.LifeLineIterator; import org.eclipse.uml2.diagram.sequence.internal.layout.vertical.input.OrderingConstraint; class ConstraintsProcessor { private static final boolean loggable = false; ConstraintsProcessor(final LifeLine[] lifeLines) { myLifeLine2lifeLineData = new HashMap<LifeLine, LifelineData>(); myLifelineDataList = new ArrayList<LifelineData>(lifeLines.length); for (int i = 0; i < lifeLines.length; i++) { LifelineData lifeLineData = new LifelineData(lifeLines[i]); myLifeLine2lifeLineData.put(lifeLines[i], lifeLineData); myLifelineDataList.add(lifeLineData); } myCurrentState = new AbstractList<LifeLineElement>() { public LifeLineElement get(int pos) { LifelineData lifeLineData = (LifelineData) myLifelineDataList.get(pos); return lifeLineData.getCurrentElement(); } public int size() { return lifeLines.length; } }; new ArrayList<LifeLineElement>(myCurrentState); } class SatisfyCondition { SatisfyCondition(LifelineSatisfyCondition[] lifelineSatisfyConitions) { myLifelineSatisfyConditions = lifelineSatisfyConitions; for (int i = 0; i < lifelineSatisfyConitions.length; i++) { if (lifelineSatisfyConitions[i] == null) { continue; } LifelineData data = (LifelineData) myLifelineDataList.get(i); if (data.getLifeLine() != lifelineSatisfyConitions[i].getLifeline()) { throw new IllegalArgumentException("Wrong Lifeline of satisfy condition"); } } } /** * @return index or -1 */ int whichLifelineStepDown() { if (myLastCheckedMemory != -1) { if (!checkLifelineSatisfying(myLastCheckedMemory)) { return myLastCheckedMemory; } } for (int i = 0; i < myLifelineSatisfyConditions.length; i++) { if (!checkLifelineSatisfying(i)) { myLastCheckedMemory = i; return i; } } return -1; } public String toString() { return NLS.bind("SatisfyCondition for {0}", new Object[] { Arrays.asList(myLifelineSatisfyConditions) }); } private boolean checkLifelineSatisfying(int index) { LifelineSatisfyCondition lifelineCondition = myLifelineSatisfyConditions[index]; if (lifelineCondition == null) { return true; } LifelineElementTraceable traceableElement = (LifelineElementTraceable) getCurrentState().get(index); if (traceableElement == null) { if (loggable) { System.out.println("[ConstraintsProcessor.checkLifelineSatisfying] I.e. we even not started processing this lifeline; condition is bad then"); } return false; } boolean res = lifelineCondition.isSatisfied(traceableElement); if (loggable) { if (!res) { System.out.println(// "[ConstraintsProcessor.checkLifelineSatisfying] traceable element " + traceableElement + // " with entity " + DebugFormat.debugFormatEntity(traceableElement.getEntityAfterElement()) + // " is not satisfying on " + myLifelineDataList.get(index) + // " because of " + lifelineCondition); } } return res; } private int myLastCheckedMemory = -1; private final LifelineSatisfyCondition[] myLifelineSatisfyConditions; } private interface StopSignal { StopSignal addAnother(StopSignal stopSignal); } private interface ProcessingDriver { StopSignal shouldResolveConstraintForElement(LifeLineElement constraintedElement, LifelineData initialLifeLineData); StopSignal shouldProcessAfterElement(LifeLineElement element); void lifelineStopped(LifelineData lifeLineData, StopSignal stopSignal); void lifeLineFinished(LifelineData lifeLineData) throws EvaluatingException; Todo chooseTodo() throws EvaluatingException; void elementFound(LifeLineElement element, LifelineData lifeLineData); interface Todo { LifelineData getLifeLineData(); void saveProcessingResult(StopSignal stopSignal) throws EvaluatingException; } } private void processLifeLines(ProcessingDriver processingDriver) throws EvaluatingException { ProcessLifeLineSession processLifeLineSession = new ProcessLifeLineSession(); while (true) { ProcessingDriver.Todo todo = processingDriver.chooseTodo(); if (todo == null) { break; } LifelineData lifeLineData = todo.getLifeLineData(); StopSignal stopSignal = processLifeLineSession.processLifeLine(processingDriver, lifeLineData, null); todo.saveProcessingResult(stopSignal); } } private class ProcessLifeLineSession { public StopSignal processLifeLine(ProcessingDriver processingDriver, LifelineData lifeLineData, final Object requiredConstraint) throws EvaluatingException { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] >>> lifeLineData=" + lifeLineData); System.out.println("[ConstraintsProcessor.processLifeLine] requiredConstraint=" + requiredConstraint); } try { if (isLocked(lifeLineData)) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] lifeLine is locked: " + lifeLineData); } throw new EvaluatingException("Lifeline is blocked; probably because of cyclic dependencies in constraints"); } while (lifeLineData.hasNext()) { LifeLineElement element = lifeLineData.nextElement(); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] next element: " + element); } processingDriver.elementFound(element, lifeLineData); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] lock " + lifeLineData); } boolean requiredOrderingConstraintFound = false; { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] check 'after' constraints"); } for (Enumeration<OrderingConstraint> orderingConstraintEnum = element.afterConstraints(); orderingConstraintEnum.hasMoreElements();) { OrderingConstraint orderingConstraint = orderingConstraintEnum.nextElement(); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] orderingConstraint=" + orderingConstraint); } if (orderingConstraint == requiredConstraint) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] it is required!"); } requiredOrderingConstraintFound = true; } else { lifeLineData.getFoundAfterConstraintSet().add(orderingConstraint); } } if (requiredOrderingConstraintFound) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] required constraint (ordering) met, return"); } } } setLocked(lifeLineData, true); boolean doNotUnlock = false; try { StopSignal stopSignal = null; { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] checking 'before' constraints"); } for (Enumeration<OrderingConstraint> orderingConstraintEnum = element.beforeConstraints(); orderingConstraintEnum.hasMoreElements();) { OrderingConstraint orderingConstraint = orderingConstraintEnum.nextElement(); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] orderingConstraint=" + orderingConstraint); } LifeLineElement constraintedElement = orderingConstraint.getBeforeElement(); LifeLine otherLifeLine = constraintedElement.getLifeLine(); if (otherLifeLine == lifeLineData.getLifeLine()) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] self-constraint"); } boolean res = lifeLineData.getFoundAfterConstraintSet().remove(orderingConstraint); if (!res) { throw new EvaluatingException("Cannot find 'before' element of one-lifeline ordering constraint"); } continue; } LifelineData otherLifeLineData = getLifeLineData(otherLifeLine); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] constraintedElement=" + constraintedElement + " from " + otherLifeLineData); } boolean res = otherLifeLineData.getFoundAfterConstraintSet().remove(orderingConstraint); if (res) { // all OK if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] 'before' element has been already met on other lifeline"); } } else { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] should wait for 'before' element"); } StopSignal subSignal; subSignal = processingDriver.shouldResolveConstraintForElement(constraintedElement, lifeLineData); if (subSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] stop resolving on this element (1), subsignal=" + subSignal); } stopSignal = subSignal.addAnother(stopSignal); continue; } if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] processing another lifeline " + otherLifeLineData); } subSignal = processLifeLine(processingDriver, otherLifeLineData, orderingConstraint); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] processing subSignal = " + subSignal); } if (subSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] stop resolving on this element (2)"); } stopSignal = subSignal.addAnother(stopSignal); continue; } } } } HorizontalConstraint horizontalConstraint = element.getHorizontalConstraint(); if (horizontalConstraint != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] horizontalConstraint=" + horizontalConstraint); } if (horizontalConstraint == requiredConstraint) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] required constraint met, return"); } return null; } if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] starting resolving " + horizontalConstraint.getLifeLineElementsList().size() + " elements"); } List<LifelineData> processedFreeLifelineDatas = new ArrayList<LifelineData>(3); for (LifeLineElement constraintedElement : horizontalConstraint.getLifeLineElementsList()) { if (element != constraintedElement) { LifelineData otherLifeLineData = getLifeLineData(constraintedElement.getLifeLine()); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] constraintedElement=" + constraintedElement + " from " + otherLifeLineData); } StopSignal subSignal; subSignal = processingDriver.shouldResolveConstraintForElement(constraintedElement, lifeLineData); if (subSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] stop resolving on this element (3) subSignal=" + subSignal); } stopSignal = subSignal.addAnother(stopSignal); continue; } if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] processing another lifeline " + otherLifeLineData); } subSignal = processLifeLine(processingDriver, otherLifeLineData, horizontalConstraint); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] processing subSignal = " + subSignal); } if (subSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] stop resolving on this element (4)"); } stopSignal = subSignal.addAnother(stopSignal); continue; } processedFreeLifelineDatas.add(otherLifeLineData); } } if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] resolving horizontal constraint finished"); } if (stopSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] some lifelines should be stopped"); } for (int i = 0; i < processedFreeLifelineDatas.size(); i++) { LifelineData freeLifeLineData = (LifelineData) processedFreeLifelineDatas.get(i); if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] stop free lifeline " + freeLifeLineData); } processingDriver.lifelineStopped(freeLifeLineData, stopSignal); } } } if (stopSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] resolving stopped on lifeline " + lifeLineData); } doNotUnlock = true; processingDriver.lifelineStopped(lifeLineData, stopSignal); return stopSignal; } if (requiredOrderingConstraintFound) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] required ordering constraint was met, return"); } return null; } } finally { if (!doNotUnlock) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] unlock lifeline " + lifeLineData); } setLocked(lifeLineData, false); } } { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] ask driver about current element"); } StopSignal stopSignal = processingDriver.shouldProcessAfterElement(element); if (stopSignal != null) { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] stop on this element and lock lifeline " + lifeLineData + " with signal " + stopSignal); } return stopSignal; } } } if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] lifeLine finished"); } processingDriver.lifeLineFinished(lifeLineData); return null; } finally { if (loggable) { System.out.println("[ConstraintsProcessor.processLifeLine] <<<"); } } } private void setLocked(LifelineData lifeLineData, boolean lock) { if (lock) { if (!myLockedLifeLineDatas.add(lifeLineData)) { throw new RuntimeException("LifeLineData is already locked"); } } else { myLockedLifeLineDatas.remove(lifeLineData); } } private boolean isLocked(LifelineData lifeLineData) { return myLockedLifeLineDatas.contains(lifeLineData); } private final Set<LifelineData> myLockedLifeLineDatas = new HashSet<LifelineData>(); } List<LifeLineElement> getCurrentState() { return myCurrentState; } private LifelineData getLifeLineData(LifeLine lifeLine) { LifelineData result = (LifelineData) myLifeLine2lifeLineData.get(lifeLine); if (result == null) { throw new RuntimeException(NLS.bind("Cannot find lifeline data for {0}", new Object[] { lifeLine })); } return result; } private static class LifelineData { LifelineData(LifeLine lifeLine) { myLifeLine = lifeLine; myLifeLineIterator = lifeLine.iterator(); } LifeLineElement getCurrentElement() { return myCurrentElement; } LifeLineElement nextElement() { myCurrentElement = myLifeLineIterator.nextElement(); return myCurrentElement; } boolean hasNext() { return myLifeLineIterator.hasNext(); } LifeLine getLifeLine() { return myLifeLine; } public String toString() { return "LLData:" + myLifeLine; } Set<OrderingConstraint> getFoundAfterConstraintSet() { return myFoundAfterConstraintSet; } private final LifeLine myLifeLine; private final LifeLineIterator myLifeLineIterator; private final Set<OrderingConstraint> myFoundAfterConstraintSet = new HashSet<OrderingConstraint>(5); private LifeLineElement myCurrentElement; } private final Map<LifeLine, LifelineData> myLifeLine2lifeLineData; private final List<LifelineData> myLifelineDataList; private final List<LifeLineElement> myCurrentState; private static class SingleInstanceStopSignal implements StopSignal { SingleInstanceStopSignal(String tag) { myTag = tag; } public StopSignal addAnother(StopSignal stopSignal) { if (stopSignal != null && stopSignal != this) { throw new RuntimeException("Inconsistent stop signal"); } return this; } public String toString() { return myTag; } private final String myTag; }; static class MultipleElementsProcessor { MultipleElementsProcessor(ConstraintsProcessor constraintsProcessor, Collection<LifeLineElement> controlLifelineElementList) { myConstraintsProcessor = constraintsProcessor; myLifelineElements = new HashSet<LifeLineElement>(controlLifelineElementList.size()); myMovableLifelineDatas = new HashSet<LifelineData>(myConstraintsProcessor.myLifeLine2lifeLineData.values()); Set<LifeLine> constraintedLifelines = new HashSet<LifeLine>(controlLifelineElementList.size()); for (LifeLineElement lifeLineElement : controlLifelineElementList) { myLifelineElements.add(lifeLineElement); boolean res = constraintedLifelines.add(lifeLineElement.getLifeLine()); if (!res) { throw new RuntimeException(NLS.bind("Lifeline is constrained twice: {0}", new Object[] { lifeLineElement.getLifeLine() })); } } myUnreachedLifelineElements = new HashSet<LifeLineElement>(myLifelineElements); } void processTo() throws EvaluatingException { ProcessingDriver processingDriver = new ProcessToDriver(); myConstraintsProcessor.processLifeLines(processingDriver); } void processHolding() throws EvaluatingException { ProcessingDriver processingDriver = new ProcessHoldingDriver(); myConstraintsProcessor.processLifeLines(processingDriver); for (LifeLineElement element : myLifelineElements) { LifelineData data = myConstraintsProcessor.getLifeLineData(element.getLifeLine()); // shift forward, to make result range containing of non-equal ends data.nextElement(); } } void processTo(SatisfyCondition satisfyCondition) throws EvaluatingException { if (loggable) { System.out.println("[ConstraintsProcessor.processTo] >>> satisfying"); System.out.println("[ConstraintsProcessor.processTo] ----------------------------------->"); System.out.println("[ConstraintsProcessor.processTo] start ProcessToDriver"); System.out.println("[ConstraintsProcessor.processTo] myLifelineElements=" + myLifelineElements); } ProcessingDriver processingDriver = new ProcessToDriver(); myConstraintsProcessor.processLifeLines(processingDriver); if (loggable) { System.out.println("[ConstraintsProcessor.processTo] start ProcessSatisfyingDriver"); System.out.println("[ConstraintsProcessor.processTo] satisfyCondition=" + satisfyCondition); } ProcessSatisfyingDriver processSatisfyingDriver = new ProcessSatisfyingDriver(satisfyCondition); myConstraintsProcessor.processLifeLines(processSatisfyingDriver); if (loggable) { System.out.println("[ConstraintsProcessor.processTo] -----------------------------------<"); System.out.println("[ConstraintsProcessor.processTo] <<< satisfying"); } } private final Set<LifeLineElement> myUnreachedLifelineElements; private final Set<LifeLineElement> myLifelineElements; private final Set<LifelineData> myMovableLifelineDatas; private final ConstraintsProcessor myConstraintsProcessor; private abstract class DriverBase implements ProcessingDriver { public StopSignal shouldResolveConstraintForElement(LifeLineElement constraintedElement, LifelineData initialLifeLineData) { LifelineData data = myConstraintsProcessor.getLifeLineData(constraintedElement.getLifeLine()); if (myMovableLifelineDatas.contains(data)) { return null; } else { return OTHER_LIFELINE_FIXED_SIGNAL; } } public StopSignal shouldProcessAfterElement(LifeLineElement element) { if (myLifelineElements.contains(element)) { LifeLine lifeline = element.getLifeLine(); if (lifeline == myActiveLifeline) { if (loggable) { System.out.println("[ConstraintsProcessor.shouldProcessAfterElement] on that lifeline"); } return ELEMENT_REACHED_SIGNAL; } else { return OTHER_LIFELINE_FIXED_SIGNAL; } } else { return null; } } public void elementFound(LifeLineElement element, LifelineData lifeLineData) { boolean res = myUnreachedLifelineElements.remove(element); if (res) { if (loggable) { System.out.println("[ConstraintsProcessor.elementFound] found required element " + element); } myMovableLifelineDatas.remove(lifeLineData); } } protected void setActiveLifeline(LifeLine activeLifeline) { myActiveLifeline = activeLifeline; } private LifeLine myActiveLifeline; } private class ProcessToDriver extends DriverBase { public void lifeLineFinished(LifelineData lifeLineData) throws EvaluatingException { throw new EvaluatingException(NLS.bind("Failed to find required element on lifeline {0}", new Object[] { lifeLineData })); } public void lifelineStopped(LifelineData lifelineData, StopSignal stopSignal) { } public Todo chooseTodo() { if (myUnreachedLifelineElements.isEmpty()) { return null; } LifeLineElement nextAnchorElement = (LifeLineElement) myUnreachedLifelineElements.iterator().next(); final LifelineData lifeLineData = myConstraintsProcessor.getLifeLineData(nextAnchorElement.getLifeLine()); if (loggable) { System.out.println("[ProcessToDriver.chooseTodo] starting required lifeline " + lifeLineData + " till element " + nextAnchorElement); } setActiveLifeline(lifeLineData.getLifeLine()); return new Todo() { public LifelineData getLifeLineData() { return lifeLineData; } public void saveProcessingResult(StopSignal stopSignal) throws CannotAlignElementsException { if (stopSignal == OTHER_LIFELINE_FIXED_SIGNAL) { throw new CannotAlignElementsException("Failed to keep all control points aligned"); } setActiveLifeline(null); } }; } } private class ProcessHoldingDriver extends DriverBase { public void lifeLineFinished(LifelineData lifeLineData) throws EvaluatingException { } public void lifelineStopped(LifelineData lifelineData, StopSignal stopSignal) { myMovableLifelineDatas.remove(lifelineData); } public Todo chooseTodo() { if (myMovableLifelineDatas.isEmpty()) { return null; } final LifelineData lifeLineData = (LifelineData) myMovableLifelineDatas.iterator().next(); setActiveLifeline(lifeLineData.getLifeLine()); if (loggable) { System.out.println("[ProcessHoldingDriver.chooseTodo] starting movable lifeline " + lifeLineData); } return new Todo() { public LifelineData getLifeLineData() { return lifeLineData; } public void saveProcessingResult(StopSignal stopSignal) { setActiveLifeline(null); } }; } } private class ProcessSatisfyingDriver extends DriverBase { ProcessSatisfyingDriver(SatisfyCondition satisfyCondition) { mySatisfyCondition = satisfyCondition; } public void lifeLineFinished(LifelineData lifeLineData) throws EvaluatingException { throw new EvaluatingException(NLS.bind("Failed to find good location on lifeline {0}", new Object[] { lifeLineData })); } public void lifelineStopped(LifelineData lifelineData, StopSignal stopSignal) { } public StopSignal shouldProcessAfterElement(LifeLineElement element) { StopSignal superResult = super.shouldProcessAfterElement(element); if (superResult != null) { return superResult; } if (element.getLifeLine() == myUnsatisfiedLifelineData.getLifeLine()) { return ELEMENT_REACHED_SIGNAL; } else { return null; } } public Todo chooseTodo() throws EvaluatingException { if (loggable) { System.out.println("[ConstraintsProcessor.ProcessSatisfyingDriver.chooseTodo] "); } int unsatisfiedLifelineIndex = mySatisfyCondition.whichLifelineStepDown(); if (loggable) { System.out.println("[ConstraintsProcessor.ProcessSatisfyingDriver.chooseTodo] unsatisfiedLifelineIndex=" + unsatisfiedLifelineIndex); } if (unsatisfiedLifelineIndex == -1) { return null; } LifelineData unsatisfiedLifelineData = (LifelineData) myConstraintsProcessor.myLifelineDataList.get(unsatisfiedLifelineIndex); if (!myMovableLifelineDatas.contains(unsatisfiedLifelineData)) { throw new EvaluatingException(NLS.bind("Lifeline condition is not satisfied for {0}, but lifeline is locked by control element", new Object[] { unsatisfiedLifelineData })); } myUnsatisfiedLifelineData = unsatisfiedLifelineData; return new Todo() { public LifelineData getLifeLineData() { return myUnsatisfiedLifelineData; } public void saveProcessingResult(StopSignal stopSignal) throws EvaluatingException { try { if (stopSignal == OTHER_LIFELINE_FIXED_SIGNAL) { throw new EvaluatingException(NLS.bind("Failed to find good location on lifeline {0}", new Object[] { myUnsatisfiedLifelineData })); } } finally { myUnsatisfiedLifelineData = null; } } }; } private final SatisfyCondition mySatisfyCondition; private LifelineData myUnsatisfiedLifelineData; } private static final StopSignal OTHER_LIFELINE_FIXED_SIGNAL = new SingleInstanceStopSignal("OTHER_LIFELINE_FIXED_SIGNAL"); private static final StopSignal ELEMENT_REACHED_SIGNAL = new SingleInstanceStopSignal("ELEMENT_REACHED_SIGNAL"); } static class UnsatisfiedDependencyException extends EvaluatingException { private static final long serialVersionUID = 1L; protected UnsatisfiedDependencyException() { super(); } protected UnsatisfiedDependencyException(String message) { super(message); } protected UnsatisfiedDependencyException(String message, Throwable cause) { super(message, cause); } protected UnsatisfiedDependencyException(Throwable cause) { super(cause); } } }