Coverage Report - org.apache.commons.scxml.SCXMLHelper

Classes in this File Line Coverage Branch Coverage Complexity
SCXMLHelper
74% 
82% 
7.077

 1  
 /*
 2  
  * Licensed to the Apache Software Foundation (ASF) under one or more
 3  
  * contributor license agreements.  See the NOTICE file distributed with
 4  
  * this work for additional information regarding copyright ownership.
 5  
  * The ASF licenses this file to You under the Apache License, Version 2.0
 6  
  * (the "License"); you may not use this file except in compliance with
 7  
  * the License.  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  
 package org.apache.commons.scxml;
 18  
 
 19  
 import java.util.HashSet;
 20  
 import java.util.IdentityHashMap;
 21  
 import java.util.Iterator;
 22  
 import java.util.List;
 23  
 import java.util.Map;
 24  
 import java.util.Set;
 25  
 
 26  
 import org.apache.commons.logging.Log;
 27  
 import org.apache.commons.logging.LogFactory;
 28  
 import org.apache.commons.scxml.model.Data;
 29  
 import org.apache.commons.scxml.model.Datamodel;
 30  
 import org.apache.commons.scxml.model.Parallel;
 31  
 import org.apache.commons.scxml.model.Path;
 32  
 import org.apache.commons.scxml.model.State;
 33  
 import org.apache.commons.scxml.model.Transition;
 34  
 import org.apache.commons.scxml.model.TransitionTarget;
 35  
 import org.apache.commons.scxml.semantics.ErrorConstants;
 36  
 import org.w3c.dom.CharacterData;
 37  
 import org.w3c.dom.Node;
 38  
 import org.w3c.dom.Text;
 39  
 
 40  
 /**
 41  
  * Helper class, all methods static final.
 42  
  *
 43  
  */
 44  
 public final class SCXMLHelper {
 45  
 
 46  
     /**
 47  
      * Return true if the string is empty.
 48  
      *
 49  
      * @param attr The String to test
 50  
      * @return Is string empty
 51  
      */
 52  
     public static boolean isStringEmpty(final String attr) {
 53  1546
         if (attr == null || attr.trim().length() == 0) {
 54  762
             return true;
 55  
         }
 56  784
         return false;
 57  
     }
 58  
 
 59  
     /**
 60  
      * Checks whether a transition target tt (State or Parallel) is a
 61  
      * descendant of the transition target context.
 62  
      *
 63  
      * @param tt
 64  
      *            TransitionTarget to check - a potential descendant
 65  
      * @param ctx
 66  
      *            TransitionTarget context - a potential ancestor
 67  
      * @return true iff tt is a descendant of ctx, false otherwise
 68  
      */
 69  
     public static boolean isDescendant(final TransitionTarget tt,
 70  
             final TransitionTarget ctx) {
 71  578
         TransitionTarget parent = tt.getParent();
 72  904
         while (parent != null) {
 73  493
             if (parent == ctx) {
 74  167
                 return true;
 75  
             }
 76  326
             parent = parent.getParent();
 77  
         }
 78  411
         return false;
 79  
     }
 80  
 
 81  
     /**
 82  
      * Creates a set which contains given states and all their ancestors
 83  
      * recursively up to the upper bound. Null upperBound means root
 84  
      * of the state machine.
 85  
      *
 86  
      * @param states The Set of States
 87  
      * @param upperBounds The Set of upper bound States
 88  
      * @return transitive closure of a given state set
 89  
      */
 90  
     public static Set getAncestorClosure(final Set states,
 91  
             final Set upperBounds) {
 92  906
         Set closure = new HashSet(states.size() * 2);
 93  906
         for (Iterator i = states.iterator(); i.hasNext();) {
 94  781
             TransitionTarget tt = (TransitionTarget) i.next();
 95  1916
             while (tt != null) {
 96  1270
                 if (!closure.add(tt)) {
 97  
                     //tt is already a part of the closure
 98  29
                     break;
 99  
                 }
 100  1241
                 if (upperBounds != null && upperBounds.contains(tt)) {
 101  106
                     break;
 102  
                 }
 103  1135
                 tt = tt.getParent();
 104  
             }
 105  
         }
 106  906
         return closure;
 107  
     }
 108  
 
 109  
     /**
 110  
      * Checks whether a given set of states is a legal Harel State Table
 111  
      * configuration (with the respect to the definition of the OR and AND
 112  
      * states).
 113  
      *
 114  
      * @param states
 115  
      *            a set of states
 116  
      * @param errRep
 117  
      *            ErrorReporter to report detailed error info if needed
 118  
      * @return true if a given state configuration is legal, false otherwise
 119  
      */
 120  
     public static boolean isLegalConfig(final Set states,
 121  
             final ErrorReporter errRep) {
 122  
         /*
 123  
          * For every active state we add 1 to the count of its parent. Each
 124  
          * Parallel should reach count equal to the number of its children and
 125  
          * contribute by 1 to its parent. Each State should reach count exactly
 126  
          * 1. SCXML elemnt (top) should reach count exactly 1. We essentially
 127  
          * summarize up the hierarchy tree starting with a given set of
 128  
          * states = active configuration.
 129  
          */
 130  270
         boolean legalConfig = true; // let's be optimists
 131  270
         Map counts = new IdentityHashMap();
 132  270
         Set scxmlCount = new HashSet();
 133  270
         for (Iterator i = states.iterator(); i.hasNext();) {
 134  283
             TransitionTarget tt = (TransitionTarget) i.next();
 135  283
             TransitionTarget parent = null;
 136  501
             while ((parent = tt.getParent()) != null) {
 137  218
                 HashSet cnt = (HashSet) counts.get(parent);
 138  218
                 if (cnt == null) {
 139  193
                     cnt = new HashSet();
 140  193
                     counts.put(parent, cnt);
 141  
                 }
 142  218
                 cnt.add(tt);
 143  218
                 tt = parent;
 144  
             }
 145  
             //top-level contribution
 146  283
             scxmlCount.add(tt);
 147  
         }
 148  
         //Validate counts:
 149  270
         for (Iterator i = counts.entrySet().iterator(); i.hasNext();) {
 150  193
             Map.Entry entry = (Map.Entry) i.next();
 151  193
             TransitionTarget tt = (TransitionTarget) entry.getKey();
 152  193
             Set count = (Set) entry.getValue();
 153  193
             if (tt instanceof Parallel) {
 154  11
                 Parallel p = (Parallel) tt;
 155  11
                 if (count.size() < p.getStates().size()) {
 156  1
                     errRep.onError(ErrorConstants.ILLEGAL_CONFIG,
 157  
                         "Not all AND states active for parallel "
 158  
                         + p.getId(), entry);
 159  1
                     legalConfig = false;
 160  
                 }
 161  
             } else {
 162  182
                 if (count.size() > 1) {
 163  1
                     errRep.onError(ErrorConstants.ILLEGAL_CONFIG,
 164  
                         "Multiple OR states active for state "
 165  
                         + tt.getId(), entry);
 166  1
                     legalConfig = false;
 167  
                 }
 168  
             }
 169  193
             count.clear(); //cleanup
 170  
         }
 171  270
         if (scxmlCount.size() > 1) {
 172  1
             errRep.onError(ErrorConstants.ILLEGAL_CONFIG,
 173  
                     "Multiple top-level OR states active!", scxmlCount);
 174  
         }
 175  
         //cleanup
 176  270
         scxmlCount.clear();
 177  270
         counts.clear();
 178  270
         return legalConfig;
 179  
     }
 180  
 
 181  
     /**
 182  
      * Finds the least common ancestor of transition targets tt1 and tt2 if
 183  
      * one exists.
 184  
      *
 185  
      * @param tt1 First TransitionTarget
 186  
      * @param tt2 Second TransitionTarget
 187  
      * @return closest common ancestor of tt1 and tt2 or null
 188  
      */
 189  
     public static TransitionTarget getLCA(final TransitionTarget tt1,
 190  
             final TransitionTarget tt2) {
 191  109
         if (tt1 == tt2) {
 192  2
             return tt1; //self-transition
 193  107
         } else if (isDescendant(tt1, tt2)) {
 194  2
             return tt2;
 195  105
         } else if (isDescendant(tt2, tt1)) {
 196  3
             return tt1;
 197  
         }
 198  102
         Set parents = new HashSet();
 199  102
         TransitionTarget tmp = tt1;
 200  157
         while ((tmp = tmp.getParent()) != null) {
 201  55
             if (tmp instanceof State) {
 202  47
                 parents.add(tmp);
 203  
             }
 204  
         }
 205  102
         tmp = tt2;
 206  109
         while ((tmp = tmp.getParent()) != null) {
 207  42
             if (tmp instanceof State) {
 208  
                 //test redundant add = common ancestor
 209  38
                 if (!parents.add(tmp)) {
 210  35
                     parents.clear();
 211  35
                     return tmp;
 212  
                 }
 213  
             }
 214  
         }
 215  67
         return null;
 216  
     }
 217  
 
 218  
     /**
 219  
      * Returns the set of all states (and parallels) which are exited if a
 220  
      * given transition t is going to be taken.
 221  
      * Current states are necessary to be taken into account
 222  
      * due to orthogonal states and cross-region transitions -
 223  
      * see UML specs for more details.
 224  
      *
 225  
      * @param t
 226  
      *            transition to be taken
 227  
      * @param currentStates
 228  
      *            the set of current states (simple states only)
 229  
      * @return a set of all states (including composite) which are exited if a
 230  
      *         given transition is taken
 231  
      */
 232  
     public static Set getStatesExited(final Transition t,
 233  
             final Set currentStates) {
 234  115
         Set allStates = new HashSet();
 235  115
         Path p = t.getPath();
 236  
         //the easy part
 237  115
         allStates.addAll(p.getUpwardSegment());
 238  115
         TransitionTarget source = t.getParent();
 239  115
         for (Iterator act = currentStates.iterator(); act.hasNext();) {
 240  119
             TransitionTarget a = (TransitionTarget) act.next();
 241  119
             if (isDescendant(a, source)) {
 242  11
                 boolean added = false;
 243  11
                 added = allStates.add(a);
 244  27
                 while (added && a != source) {
 245  16
                     a = a.getParent();
 246  16
                     added = allStates.add(a);
 247  
                 }
 248  
             }
 249  
         }
 250  115
         if (p.isCrossRegion()) {
 251  0
             for (Iterator regions = p.getRegionsExited().iterator();
 252  0
                     regions.hasNext();) {
 253  0
                 Parallel par = ((Parallel) ((State) regions.next()).
 254  
                     getParent());
 255  
                 //let's find affected states in sibling regions
 256  0
                 for (Iterator siblings = par.getStates().iterator();
 257  0
                         siblings.hasNext();) {
 258  0
                     State s = (State) siblings.next();
 259  0
                     for (Iterator act = currentStates.iterator();
 260  0
                             act.hasNext();) {
 261  0
                         TransitionTarget a = (TransitionTarget) act.next();
 262  0
                         if (isDescendant(a, s)) {
 263  
                             //a is affected
 264  0
                             boolean added = false;
 265  0
                             added = allStates.add(a);
 266  0
                             while (added && a != s) {
 267  0
                                 a = a.getParent();
 268  0
                                 added = allStates.add(a);
 269  
                             }
 270  
                         }
 271  
                     }
 272  
                 }
 273  
             }
 274  
         }
 275  115
         return allStates;
 276  
     }
 277  
 
 278  
     /**
 279  
      * According to the UML definition, two transitions
 280  
      * are conflicting if the sets of states they exit overlap.
 281  
      *
 282  
      * @param t1 a transition to check against t2
 283  
      * @param t2 a transition to check against t1
 284  
      * @param currentStates the set of current states (simple states only)
 285  
      * @return true if the t1 and t2 are conflicting transitions
 286  
      * @see #getStatesExited(Transition, Set)
 287  
      */
 288  
     public static boolean inConflict(final Transition t1,
 289  
             final Transition t2, final Set currentStates) {
 290  0
         Set ts1 = getStatesExited(t1, currentStates);
 291  0
         Set ts2 = getStatesExited(t2, currentStates);
 292  0
         ts1.retainAll(ts2);
 293  0
         if (ts1.isEmpty()) {
 294  0
             return false;
 295  
         }
 296  0
         return true;
 297  
     }
 298  
 
 299  
     /**
 300  
      * Whether the first argument is a subtype of the second.
 301  
      *
 302  
      * @param child The candidate subtype
 303  
      * @param parent The supertype
 304  
      * @return true if child is subtype of parent, otherwise false
 305  
      */
 306  
     public static boolean subtypeOf(final Class child, final Class parent) {
 307  0
         if (child == null || parent == null) {
 308  0
             return false;
 309  
         }
 310  1
         for (Class current = child; current != Object.class;
 311  0
                 current = current.getSuperclass()) {
 312  0
             if (current == parent) {
 313  0
                 return true;
 314  
             }
 315  
         }
 316  0
         return false;
 317  
     }
 318  
 
 319  
     /**
 320  
      * Whether the class implements the interface.
 321  
      *
 322  
      * @param clas The candidate class
 323  
      * @param interfayce The interface
 324  
      * @return true if clas implements interfayce, otherwise false
 325  
      */
 326  
     public static boolean implementationOf(final Class clas,
 327  
             final Class interfayce) {
 328  25
         if (clas == null || interfayce == null || !interfayce.isInterface()) {
 329  0
             return false;
 330  
         }
 331  25
         for (Class current = clas; current != Object.class;
 332  50
                 current = current.getSuperclass()) {
 333  50
             Class[] implementedInterfaces = current.getInterfaces();
 334  125
             for (int i = 0; i < implementedInterfaces.length; i++) {
 335  75
                 if (implementedInterfaces[i] == interfayce) {
 336  0
                     return true;
 337  
                 }
 338  
             }
 339  
         }
 340  25
         return false;
 341  
     }
 342  
 
 343  
     /**
 344  
      * Set node value, depending on its type, from a String.
 345  
      *
 346  
      * @param node A Node whose value is to be set
 347  
      * @param value The new value
 348  
      */
 349  
     public static void setNodeValue(final Node node, final String value) {
 350  8
         switch(node.getNodeType()) {
 351  
             case Node.ATTRIBUTE_NODE:
 352  0
                 node.setNodeValue(value);
 353  0
                 break;
 354  
             case Node.ELEMENT_NODE:
 355  
                 //remove all text children
 356  8
                 if (node.hasChildNodes()) {
 357  8
                     Node child = node.getFirstChild();
 358  16
                     while (child != null) {
 359  8
                         if (child.getNodeType() == Node.TEXT_NODE) {
 360  8
                             node.removeChild(child);
 361  
                         }
 362  8
                         child = child.getNextSibling();
 363  
                     }
 364  
                 }
 365  
                 //create a new text node and append
 366  8
                 Text txt = node.getOwnerDocument().createTextNode(value);
 367  8
                 node.appendChild(txt);
 368  8
                 break;
 369  
             case Node.TEXT_NODE:
 370  
             case Node.CDATA_SECTION_NODE:
 371  0
                 ((CharacterData) node).setData(value);
 372  0
                 break;
 373  
             default:
 374  0
                 String err = "Trying to set value of a strange Node type: "
 375  
                     + node.getNodeType();
 376  
                 //Logger.logln(Logger.E, err);
 377  0
                 throw new IllegalArgumentException(err);
 378  
         }
 379  8
     }
 380  
 
 381  
     /**
 382  
      * Retrieve a DOM node value as a string depending on its type.
 383  
      *
 384  
      * @param node A node to be retreived
 385  
      * @return The value as a string
 386  
      */
 387  
     public static String getNodeValue(final Node node) {
 388  58
         String result = "";
 389  58
         if (node == null) {
 390  0
             return result;
 391  
         }
 392  58
         switch(node.getNodeType()) {
 393  
             case Node.ATTRIBUTE_NODE:
 394  17
                 result = node.getNodeValue();
 395  17
                 break;
 396  
             case Node.ELEMENT_NODE:
 397  41
                 if (node.hasChildNodes()) {
 398  41
                     Node child = node.getFirstChild();
 399  41
                     StringBuffer buf = new StringBuffer();
 400  82
                     while (child != null) {
 401  41
                         if (child.getNodeType() == Node.TEXT_NODE) {
 402  41
                             buf.append(((CharacterData) child).getData());
 403  
                         }
 404  41
                         child = child.getNextSibling();
 405  
                     }
 406  41
                     result = buf.toString();
 407  
                 }
 408  
                 break;
 409  
             case Node.TEXT_NODE:
 410  
             case Node.CDATA_SECTION_NODE:
 411  0
                 result = ((CharacterData) node).getData();
 412  0
                 break;
 413  
             default:
 414  0
                 String err = "Trying to get value of a strange Node type: "
 415  
                     + node.getNodeType();
 416  
                 //Logger.logln(Logger.W, err );
 417  0
                 throw new IllegalArgumentException(err);
 418  
         }
 419  58
         return result.trim();
 420  
     }
 421  
 
 422  
     /**
 423  
      * Clone data model.
 424  
      *
 425  
      * @param ctx The context to clone to.
 426  
      * @param datamodel The datamodel to clone.
 427  
      * @param evaluator The expression evaluator.
 428  
      * @param log The error log.
 429  
      */
 430  
     public static void cloneDatamodel(final Datamodel datamodel,
 431  
             final Context ctx, final Evaluator evaluator,
 432  
             final Log log) {
 433  242
         if (datamodel == null) {
 434  209
             return;
 435  
         }
 436  33
         List data = datamodel.getData();
 437  33
         if (data == null) {
 438  0
             return;
 439  
         }
 440  33
         for (Iterator iter = data.iterator(); iter.hasNext();) {
 441  48
             Data datum = (Data) iter.next();
 442  48
             Node datumNode = datum.getNode();
 443  48
             Node valueNode = null;
 444  48
             if (datumNode != null) {
 445  44
                 valueNode = datumNode.cloneNode(true);
 446  
             }
 447  
             // prefer "src" over "expr" over "inline"
 448  48
             if (!SCXMLHelper.isStringEmpty(datum.getSrc())) {
 449  0
                 ctx.setLocal(datum.getName(), valueNode);
 450  48
             } else if (!SCXMLHelper.isStringEmpty(datum.
 451  
                     getExpr())) {
 452  4
                 Object value = null;
 453  
                 try {
 454  4
                     ctx.setLocal(NAMESPACES_KEY, datum.getNamespaces());
 455  4
                     value = evaluator.eval(ctx, datum.getExpr());
 456  4
                     ctx.setLocal(NAMESPACES_KEY, null);
 457  0
                 } catch (SCXMLExpressionException see) {
 458  0
                     if (log != null) {
 459  0
                         log.error(see.getMessage(), see);
 460  
                     } else {
 461  0
                         Log defaultLog = LogFactory.getLog(SCXMLHelper.class);
 462  0
                         defaultLog.error(see.getMessage(), see);
 463  
                     }
 464  4
                 }
 465  4
                 ctx.setLocal(datum.getName(), value);
 466  
             } else {
 467  44
                 ctx.setLocal(datum.getName(), valueNode);
 468  
             }
 469  
         }
 470  33
     }
 471  
 
 472  
     /**
 473  
      * Discourage instantiation since this is a utility class.
 474  
      */
 475  
     private SCXMLHelper() {
 476  0
         super();
 477  0
     }
 478  
 
 479  
     /**
 480  
      * Current document namespaces are saved under this key in the parent
 481  
      * state's context.
 482  
      */
 483  
     private static final String NAMESPACES_KEY = "_ALL_NAMESPACES";
 484  
 
 485  
 }
 486