Coverage Report - org.apache.commons.scxml.io.ModelUpdater

Classes in this File Line Coverage Branch Coverage Complexity
ModelUpdater
75% 
93% 
4.857

 1  
 /*
 2  
  *
 3  
  *   Copyright 2005-2006 The Apache Software Foundation.
 4  
  *
 5  
  *  Licensed under the Apache License, Version 2.0 (the "License");
 6  
  *  you may not use this file except in compliance with the License.
 7  
  *  You may obtain a copy of the License at
 8  
  *
 9  
  *      http://www.apache.org/licenses/LICENSE-2.0
 10  
  *
 11  
  *  Unless required by applicable law or agreed to in writing, software
 12  
  *  distributed under the License is distributed on an "AS IS" BASIS,
 13  
  *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 14  
  *  See the License for the specific language governing permissions and
 15  
  *  limitations under the License.
 16  
  *
 17  
  */
 18  
 package org.apache.commons.scxml.io;
 19  
 
 20  
 import java.text.MessageFormat;
 21  
 import java.util.Iterator;
 22  
 import java.util.List;
 23  
 import java.util.Map;
 24  
 
 25  
 import org.apache.commons.logging.LogFactory;
 26  
 import org.apache.commons.scxml.SCXMLHelper;
 27  
 import org.apache.commons.scxml.model.History;
 28  
 import org.apache.commons.scxml.model.Initial;
 29  
 import org.apache.commons.scxml.model.Invoke;
 30  
 import org.apache.commons.scxml.model.ModelException;
 31  
 import org.apache.commons.scxml.model.Parallel;
 32  
 import org.apache.commons.scxml.model.SCXML;
 33  
 import org.apache.commons.scxml.model.State;
 34  
 import org.apache.commons.scxml.model.Transition;
 35  
 import org.apache.commons.scxml.model.TransitionTarget;
 36  
 
 37  
 /**
 38  
  * The ModelUpdater provides the utility methods to check the Commons
 39  
  * SCXML model for inconsistencies, detect errors, and wire the Commons
 40  
  * SCXML model appropriately post document parsing by the digester to make
 41  
  * it executor ready.
 42  
  */
 43  
 final class ModelUpdater {
 44  
 
 45  
     /*
 46  
      * Post-processing methods to make the SCXML object SCXMLExecutor ready.
 47  
      */
 48  
     /**
 49  
      * <p>Update the SCXML object model and make it SCXMLExecutor ready.
 50  
      * This is part of post-digester processing, and sets up the necessary
 51  
      * object references throughtout the SCXML object model for the parsed
 52  
      * document.</p>
 53  
      *
 54  
      * @param scxml The SCXML object (output from Digester)
 55  
      * @throws ModelException If the object model is flawed
 56  
      */
 57  
    static void updateSCXML(final SCXML scxml) throws ModelException {
 58  
        // Watch case, slightly unfortunate naming ;-)
 59  39
        String initialstate = scxml.getInitialstate();
 60  
        //we have to use getTargets() here since the initialState can be
 61  
        //an indirect descendant
 62  
        // Concern marked by one of the code reviewers: better type check,
 63  
        //            now ClassCastException happens for Parallel
 64  
        // Response: initial should be a State, for Parallel, it is implicit
 65  39
        State initialState = (State) scxml.getTargets().get(initialstate);
 66  39
        if (initialState == null) {
 67  
            // Where do we, where do we go?
 68  0
            logAndThrowModelError(ERR_SCXML_NO_INIT, new Object[] {
 69  
                initialstate });
 70  
        }
 71  39
        scxml.setInitialState(initialState);
 72  39
        Map targets = scxml.getTargets();
 73  39
        Map states = scxml.getStates();
 74  39
        Iterator i = states.keySet().iterator();
 75  128
        while (i.hasNext()) {
 76  89
            updateState((State) states.get(i.next()), targets);
 77  
        }
 78  39
    }
 79  
 
 80  
     /**
 81  
       * Update this State object (part of post-digestion processing).
 82  
       * Also checks for any errors in the document.
 83  
       *
 84  
       * @param s The State object
 85  
       * @param targets The global Map of all transition targets
 86  
       * @throws ModelException If the object model is flawed
 87  
       */
 88  
     private static void updateState(final State s, final Map targets)
 89  
     throws ModelException {
 90  
         //ensure both onEntry and onExit have parent
 91  
         //could add next two lines as a Digester rule for OnEntry/OnExit
 92  194
         s.getOnEntry().setParent(s);
 93  194
         s.getOnExit().setParent(s);
 94  
         //initialize next / inital
 95  194
         Initial ini = s.getInitial();
 96  194
         Map c = s.getChildren();
 97  194
         TransitionTarget initialState = null;
 98  194
         if (!c.isEmpty()) {
 99  40
             if (ini == null) {
 100  0
                 logAndThrowModelError(ERR_STATE_NO_INIT,
 101  
                     new Object[] {getStateName(s)});
 102  
             }
 103  40
             Transition initialTransition = ini.getTransition();
 104  40
             updateTransition(initialTransition, targets);
 105  40
             initialState = initialTransition.getTarget();
 106  
             // we have to allow for an indirect descendant initial (targets)
 107  
             //check that initialState is a descendant of s
 108  40
             if (initialState == null
 109  
                     || !SCXMLHelper.isDescendant(initialState, s)) {
 110  0
                 logAndThrowModelError(ERR_STATE_BAD_INIT,
 111  
                     new Object[] {getStateName(s)});
 112  
             }
 113  
         }
 114  194
         List histories = s.getHistory();
 115  194
         Iterator histIter = histories.iterator();
 116  199
         while (histIter.hasNext()) {
 117  5
             if (s.isSimple()) {
 118  0
                 logAndThrowModelError(ERR_HISTORY_SIMPLE_STATE,
 119  
                     new Object[] {getStateName(s)});
 120  
             }
 121  5
             History h = (History) histIter.next();
 122  5
             Transition historyTransition = h.getTransition();
 123  5
             if (historyTransition == null) {
 124  
                 // try to assign initial as default
 125  1
                 if (initialState != null
 126  
                         && !(initialState instanceof History)) {
 127  1
                     historyTransition = new Transition();
 128  1
                     historyTransition.setNext(initialState.getId());
 129  1
                     historyTransition.setParent(h);
 130  1
                     h.setTransition(historyTransition);
 131  
                 } else {
 132  0
                     logAndThrowModelError(ERR_HISTORY_NO_DEFAULT,
 133  
                         new Object[] {h.getId(), getStateName(s)});
 134  
                 }
 135  
             }
 136  5
             updateTransition(historyTransition, targets);
 137  5
             State historyState = (State) historyTransition.getTarget();
 138  5
             if (historyState == null) {
 139  0
                 logAndThrowModelError(ERR_STATE_NO_HIST,
 140  
                     new Object[] {getStateName(s)});
 141  
             }
 142  5
             if (!h.isDeep()) {
 143  3
                 if (!c.containsValue(historyState)) {
 144  0
                     logAndThrowModelError(ERR_STATE_BAD_SHALLOW_HIST,
 145  
                         new Object[] {getStateName(s)});
 146  
                 }
 147  
             } else {
 148  2
                 if (!SCXMLHelper.isDescendant(historyState, s)) {
 149  0
                     logAndThrowModelError(ERR_STATE_BAD_DEEP_HIST,
 150  
                         new Object[] {getStateName(s)});
 151  
                 }
 152  
             }
 153  
         }
 154  194
         Map t = s.getTransitions();
 155  194
         Iterator i = t.keySet().iterator();
 156  347
         while (i.hasNext()) {
 157  153
             Iterator j = ((List) t.get(i.next())).iterator();
 158  311
             while (j.hasNext()) {
 159  158
                 Transition trn = (Transition) j.next();
 160  
                 //could add next two lines as a Digester rule for Transition
 161  158
                 trn.setParent(s);
 162  158
                 updateTransition(trn, targets);
 163  
             }
 164  
         }
 165  194
         Parallel p = s.getParallel();
 166  194
         Invoke inv = s.getInvoke();
 167  194
         if ((inv != null && p != null)
 168  
                 || (inv != null && !c.isEmpty())
 169  
                 || (p != null && !c.isEmpty())) {
 170  0
             logAndThrowModelError(ERR_STATE_BAD_CONTENTS,
 171  
                 new Object[] {getStateName(s)});
 172  
         }
 173  194
         if (p != null) {
 174  5
             updateParallel(p, targets);
 175  189
         } else if (inv != null) {
 176  1
             String ttype = inv.getTargettype();
 177  1
             if (ttype == null || ttype.trim().length() == 0) {
 178  0
                 logAndThrowModelError(ERR_INVOKE_NO_TARGETTYPE,
 179  
                     new Object[] {getStateName(s)});
 180  
             }
 181  1
             String src = inv.getSrc();
 182  1
             boolean noSrc = (src == null || src.trim().length() == 0);
 183  1
             String srcexpr = inv.getSrcexpr();
 184  1
             boolean noSrcexpr = (srcexpr == null
 185  
                                  || srcexpr.trim().length() == 0);
 186  1
             if (noSrc && noSrcexpr) {
 187  0
                 logAndThrowModelError(ERR_INVOKE_NO_SRC,
 188  
                     new Object[] {getStateName(s)});
 189  
             }
 190  1
             if (!noSrc && !noSrcexpr) {
 191  0
                 logAndThrowModelError(ERR_INVOKE_AMBIGUOUS_SRC,
 192  
                     new Object[] {getStateName(s)});
 193  
             }
 194  
         } else {
 195  188
             Iterator j = c.keySet().iterator();
 196  281
             while (j.hasNext()) {
 197  93
                 updateState((State) c.get(j.next()), targets);
 198  
             }
 199  
         }
 200  194
     }
 201  
 
 202  
     /**
 203  
       * Update this Parallel object (part of post-digestion processing).
 204  
       *
 205  
       * @param p The Parallel object
 206  
       * @param targets The global Map of all transition targets
 207  
       * @throws ModelException If the object model is flawed
 208  
       */
 209  
     private static void updateParallel(final Parallel p, final Map targets)
 210  
     throws ModelException {
 211  5
         Iterator i = p.getStates().iterator();
 212  17
         while (i.hasNext()) {
 213  12
             updateState((State) i.next(), targets);
 214  
         }
 215  5
     }
 216  
 
 217  
     /**
 218  
       * Update this Transition object (part of post-digestion processing).
 219  
       *
 220  
       * @param t The Transition object
 221  
       * @param targets The global Map of all transition targets
 222  
       * @throws ModelException If the object model is flawed
 223  
       */
 224  
     private static void updateTransition(final Transition t,
 225  
             final Map targets) throws ModelException {
 226  203
         String next = t.getNext();
 227  203
         TransitionTarget tt = t.getTarget();
 228  203
         if (tt == null) {
 229  203
             tt = (TransitionTarget) targets.get(next);
 230  203
             if (tt == null) {
 231  0
                 logAndThrowModelError(ERR_TARGET_NOT_FOUND, new Object[] {
 232  
                     next });
 233  
             }
 234  203
             t.setTarget(tt);
 235  
         }
 236  203
     }
 237  
 
 238  
     /**
 239  
       * Log an error discovered in post-digestion processing.
 240  
       *
 241  
       * @param errType The type of error
 242  
       * @param msgArgs The arguments for formatting the error message
 243  
       * @throws ModelException The model error, always thrown.
 244  
       */
 245  
     private static void logAndThrowModelError(final String errType,
 246  
             final Object[] msgArgs) throws ModelException {
 247  0
         MessageFormat msgFormat = new MessageFormat(errType);
 248  0
         String errMsg = msgFormat.format(msgArgs);
 249  0
         org.apache.commons.logging.Log log = LogFactory.
 250  0
             getLog(ModelUpdater.class);
 251  0
         log.error(errMsg);
 252  0
         throw new ModelException(errMsg);
 253  
     }
 254  
 
 255  
     /**
 256  
      * Get state identifier for error message. This method is only
 257  
      * called to produce an appropriate log message in some error
 258  
      * conditions.
 259  
      *
 260  
      * @param state The <code>State</code> object
 261  
      * @return The state identifier for the error message
 262  
      */
 263  
     private static String getStateName(final State state) {
 264  0
         String badState = "anonymous state";
 265  0
         if (!SCXMLHelper.isStringEmpty(state.getId())) {
 266  0
             badState = "state with ID \"" + state.getId() + "\"";
 267  
         }
 268  0
         return badState;
 269  
     }
 270  
 
 271  
     /**
 272  
      * Discourage instantiation since this is a utility class.
 273  
      */
 274  
     private ModelUpdater() {
 275  0
         super();
 276  0
     }
 277  
 
 278  
     //// Error messages
 279  
     /**
 280  
      * Error message when SCXML document specifies an illegal initial state.
 281  
      */
 282  
     private static final String ERR_SCXML_NO_INIT = "No SCXML child state "
 283  
         + "with ID \"{0}\" found; illegal initialstate for SCXML document";
 284  
 
 285  
     /**
 286  
      * Error message when a state element specifies an initial state which
 287  
      * cannot be found.
 288  
      */
 289  
     private static final String ERR_STATE_NO_INIT = "No initial element "
 290  
         + "available for {0}";
 291  
 
 292  
     /**
 293  
      * Error message when a state element specifies an initial state which
 294  
      * is not a direct descendent.
 295  
      */
 296  
     private static final String ERR_STATE_BAD_INIT = "Initial state "
 297  
         + "null or not a descendant of {0}";
 298  
 
 299  
     /**
 300  
      * Error message when a state element contains anything other than
 301  
      * one &lt;parallel&gt;, one &lt;invoke&gt; or any number of
 302  
      * &lt;state&gt; children.
 303  
      */
 304  
     private static final String ERR_STATE_BAD_CONTENTS = "{0} should "
 305  
         + "contain either one <parallel>, one <invoke> or any number of "
 306  
         + "<state> children.";
 307  
 
 308  
     /**
 309  
      * Error message when a referenced history state cannot be found.
 310  
      */
 311  
     private static final String ERR_STATE_NO_HIST = "Referenced history state"
 312  
         + " null for {0}";
 313  
 
 314  
     /**
 315  
      * Error message when a shallow history state is not a child state.
 316  
      */
 317  
     private static final String ERR_STATE_BAD_SHALLOW_HIST = "History state"
 318  
         + " for shallow history is not child for {0}";
 319  
 
 320  
     /**
 321  
      * Error message when a deep history state is not a descendent state.
 322  
      */
 323  
     private static final String ERR_STATE_BAD_DEEP_HIST = "History state"
 324  
         + " for deep history is not descendant for {0}";
 325  
 
 326  
     /**
 327  
      * Transition target is not a legal IDREF (not found).
 328  
      */
 329  
     private static final String ERR_TARGET_NOT_FOUND =
 330  
         "Transition target with ID \"{0}\" not found";
 331  
 
 332  
     /**
 333  
      * Simple states should not contain a history.
 334  
      */
 335  
     private static final String ERR_HISTORY_SIMPLE_STATE =
 336  
         "Simple {0} contains history elements";
 337  
 
 338  
     /**
 339  
      * History does not specify a default transition target.
 340  
      */
 341  
     private static final String ERR_HISTORY_NO_DEFAULT =
 342  
         "No default target specified for history with ID \"{0}\""
 343  
         + " belonging to {1}";
 344  
 
 345  
     /**
 346  
      * Error message when an &lt;invoke&gt; does not specify a "targettype"
 347  
      * attribute.
 348  
      */
 349  
     private static final String ERR_INVOKE_NO_TARGETTYPE = "{0} contains "
 350  
         + "<invoke> with no \"targettype\" attribute specified.";
 351  
 
 352  
     /**
 353  
      * Error message when an &lt;invoke&gt; does not specify a "src"
 354  
      * or a "srcexpr" attribute.
 355  
      */
 356  
     private static final String ERR_INVOKE_NO_SRC = "{0} contains "
 357  
         + "<invoke> without a \"src\" or \"srcexpr\" attribute specified.";
 358  
 
 359  
     /**
 360  
      * Error message when an &lt;invoke&gt; specifies both "src" and "srcexpr"
 361  
      * attributes.
 362  
      */
 363  
     private static final String ERR_INVOKE_AMBIGUOUS_SRC = "{0} contains "
 364  
         + "<invoke> with both \"src\" and \"srcexpr\" attributes specified,"
 365  
         + " must specify either one, but not both.";
 366  
 
 367  
 }
 368