// Copyright (c) 2006 - 2008, Markus Strauch. // All rights reserved. // // Redistribution and use in source and binary forms, with or without // modification, are permitted provided that the following conditions are met: // // * Redistributions of source code must retain the above copyright notice, // this list of conditions and the following disclaimer. // * Redistributions in binary form must reproduce the above copyright notice, // this list of conditions and the following disclaimer in the documentation // and/or other materials provided with the distribution. // // THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" // AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE // IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE // ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE // LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR // CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF // SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS // INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN // CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) // ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF // THE POSSIBILITY OF SUCH DAMAGE. package net.sf.sdedit.diagram; import java.util.LinkedList; import java.util.ListIterator; import net.sf.sdedit.drawable.Arrow; import net.sf.sdedit.drawable.Fragment; import net.sf.sdedit.drawable.SequenceElement; import net.sf.sdedit.error.SyntaxError; import net.sf.sdedit.message.Answer; /** * A <tt>FragmentManager</tt> is responsible for processing the fragments * occurring in a diagram. During the process, the <tt>FragmentManager</tt> * sets a fragment in three states, successively: * <ol> * <li><strong>open</strong>: sequence elements are added to the fragment</li> * <li><strong>closing</strong>: answers to included messages are still being * added to the fragment</li> * <li><strong>finished</strong>: nothing is being added anymore</li> * </ol> * * @author Markus Strauch * */ public final class FragmentManager { private final Diagram diagram; /** * This list contains the fragments that have been opened and for which we * have not yet seen a close command (see {@linkplain * DiagramDataProvider#closeFragment()}). When a sequence element is added * via {@linkplain #addSequenceElement(SequenceElement)} it will be added to * all fragments in this list. */ private final LinkedList<Fragment> openFragments; /** * This list contains the fragments for which we have already seen a close * command (see {@linkplain DiagramDataProvider#closeFragment()}). Sequence * elements added via {@linkplain #addSequenceElement(SequenceElement)} will * not be added to the comments in this list. When an answer is processed, * {@linkplain #finishFragmentsNotIncluding(Answer)} is called and all * fragments that do not include the message to which the given answer is * the answer will be finished and removed from the * <tt>closingFragments</tt> list. */ private final LinkedList<Fragment> closingFragments; /** * The list of fragment labels that have currently been opened. The list is * emptied when something else than a fragment label (for instance, a * message) is delivered by the DiagramDataProvider. */ private final LinkedList<String> fragmentLabels; /** * If this string is not null, it contains the label of the next section of * the current fragment. */ private String fragmentSectionLabel; /** * Creates a new <tt>FragmentManager</tt> responsible for the fragments * occurring in the given diagram. * * @param diagram * the given diagram */ public FragmentManager(Diagram diagram) { this.diagram = diagram; openFragments = new LinkedList<Fragment>(); closingFragments = new LinkedList<Fragment>(); fragmentLabels = new LinkedList<String>(); } /** * Returns the labels for the fragments for which we have already seen an * open command (see {@linkplain DiagramDataProvider#openFragment()}) but * for which we have not yet created a {@linkplain Fragment} representation. * * @return the labels for the fragments for which we have already seen an * open command, but for which there is not yet a <tt>Fragment</tt> * object */ public LinkedList<String> getFragmentLabels() { return fragmentLabels; } /** * When we have just seen the beginning of a new fragment section, this * method returns its label, otherwise <tt>null</tt>. * * @return the label of a new fragment section or <tt>null</tt> */ public String getFragmentSectionLabel() { return fragmentSectionLabel; } /** * This method should be called when for all fragments corresponding to * {@linkplain #getFragmentLabels()} there has been a <tt>Fragment</tt> * object created. The list is cleared then. */ public void clearLabels() { fragmentLabels.clear(); } /** * This method should be called when a new section has been just created. */ public void clearSectionLabel() { fragmentSectionLabel = null; } /** * This method uses the provider of the diagram data (see * {@linkplain DiagramDataProvider}, {@linkplain Diagram#getDataProvider()} * and takes some action if the provider says that a fragment is opened or * closed, or if the beginning of a new section is specified. * * @return true if the diagram data provider currently specifies something * that has to do with a fragment * @throws SyntaxError * if the fragment specification is syntactically wrong */ public boolean readFragments() throws SyntaxError { DiagramDataProvider provider = diagram.getDataProvider(); if (provider.closeFragment()) { closeRecentFragment(); return true; } String comment = provider.openFragment(); if (comment != null) { fragmentLabels.add(comment); return true; } String newSeparator = provider.getFragmentSeparator(); if (newSeparator != null) { if (fragmentSectionLabel != null) { throw new SyntaxError(provider, "double separator"); } if (getRecentFragment() == null) { throw new SyntaxError(provider, "no comment open"); } fragmentSectionLabel = newSeparator; return true; } return false; } /** * Creates {@linkplain Fragment} objects for all pending fragment labels in * {@linkplain #getFragmentLabels()} and adds them to the list of open * fragments. */ public void openFragments() { finishFragments(); for (String fragment : fragmentLabels) { if (fragment.startsWith("[c")) { fragment = fragment.substring(2, fragment.length() - 1).trim(); } if (fragment.length() > 0 && fragment.charAt(0) == ':') { fragment = fragment.substring(1); int s = fragment.indexOf(' '); if (s == -1) { openFragment(fragment, ""); } else { String type = fragment.substring(0, s); String name = fragment.substring(s + 1); openFragment(type, name); } } else { openFragment("", fragment); } } if (fragmentSectionLabel != null) { Fragment recent = getRecentFragment(); if (recent != null) { recent.addSection(fragmentSectionLabel); } } } private void openFragment(String type, String text) { Fragment fragment = new Fragment(type, text, diagram); int textHeight = diagram.getPaintDevice().getTextHeight(); int extension = 5 + (fragment.getCondition().length() > 0 ? textHeight * 2 : textHeight); diagram.getPaintDevice().announce( diagram.getConfiguration().getFragmentMargin() + extension); diagram.extendLifelines(diagram.getConfiguration().getFragmentMargin()); fragment.setTop(diagram.getVerticalPosition()); diagram.extendLifelines(extension); openFragments.addLast(fragment); diagram.getPaintDevice().addOtherDrawable(fragment); int l = openFragments.size() - 1; for (Fragment open : openFragments) { open.setLevel(l); l--; } } /** * Returns a flag indicating if there are fragments for which we have not * seen a close command yet (see * {@linkplain DiagramDataProvider#closeFragment()}). * * @return a flag indicating if there are fragments for which we have not * seen a close command */ public boolean openFragmentsExist() { return openFragments.size() > 0; } /** * Sets all the closing fragments into finished state. */ public void finishFragments() { for (Fragment comment : closingFragments) { finishFragment(comment); } closingFragments.clear(); } /** * Sets the bottom line of a closing fragment when there will never be added * anything to it. * * @param comment * a closing fragment such that there never will be anything * added to it */ private void finishFragment(Fragment comment) { diagram .extendLifelines(diagram.getConfiguration() .getFragmentPadding()); comment.setBottom(diagram.getVerticalPosition()); diagram.extendLifelines(diagram.getConfiguration().getFragmentMargin()); } /** * Sets the closing fragments that do not include the message to which the * given answer is the answer into finished state. * * @param answer * an answer */ public void finishFragmentsNotIncluding(Answer answer) { ListIterator<Fragment> lic = closingFragments.listIterator(); Arrow arrow = answer.getForwardMessage().getArrow(); while (lic.hasNext()) { Fragment comment = lic.next(); if (!comment.containsElement(arrow)) { lic.remove(); finishFragment(comment); } } } /** * Adds the given sequence element to all open and closing fragments. * * @param elem * a sequence element to be added to all open and closing * fragments */ public void addSequenceElement(SequenceElement elem) { for (Fragment comment : openFragments) { comment.addElement(elem); } for (Fragment comment : closingFragments) { comment.addElement(elem); } } private void closeRecentFragment() throws SyntaxError { if (openFragments.isEmpty()) { throw new SyntaxError(diagram.getDataProvider(), "There is no open comment"); } Fragment comment = openFragments.removeLast(); closingFragments.addLast(comment); } private Fragment getRecentFragment() { if (openFragments.isEmpty()) { return null; } return openFragments.getLast(); } }