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

Classes in this File Line Coverage Branch Coverage Complexity
SCXMLHelper
75% 
85% 
7.077

 1  
 /*
 2  
  *
 3  
  *   Copyright 2005 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;
 19  
 
 20  
 import java.util.HashSet;
 21  
 import java.util.IdentityHashMap;
 22  
 import java.util.Iterator;
 23  
 import java.util.List;
 24  
 import java.util.Map;
 25  
 import java.util.Set;
 26  
 
 27  
 import org.apache.commons.logging.Log;
 28  
 import org.apache.commons.logging.LogFactory;
 29  
 import org.apache.commons.scxml.model.Data;
 30  
 import org.apache.commons.scxml.model.Datamodel;
 31  
 import org.apache.commons.scxml.model.Parallel;
 32  
 import org.apache.commons.scxml.model.Path;
 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  
 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  1051
         if (attr == null || attr.trim().length() == 0) {
 54  439
             return true;
 55  
         }
 56  612
         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  482
         TransitionTarget parent = tt.getParent();
 72  787
         while (parent != null) {
 73  459
             if (parent == ctx) {
 74  154
                 return true;
 75  
             }
 76  305
             parent = parent.getParent();
 77  
         }
 78  328
         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  701
         Set closure = new HashSet(states.size() * 2);
 93  701
         for (Iterator i = states.iterator(); i.hasNext();) {
 94  612
             TransitionTarget tt = (TransitionTarget) i.next();
 95  1589
             while (tt != null) {
 96  1087
                 if (!closure.add(tt)) {
 97  
                     //tt is already a part of the closure
 98  29
                     break;
 99  
                 }
 100  1058
                 if (upperBounds != null && upperBounds.contains(tt)) {
 101  81
                     break;
 102  
                 }
 103  977
                 tt = tt.getParent();
 104  
             }
 105  
         }
 106  701
         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  209
         boolean legalConfig = true; // let's be optimists
 131  209
         Map counts = new IdentityHashMap();
 132  209
         Set scxmlCount = new HashSet();
 133  209
         for (Iterator i = states.iterator(); i.hasNext();) {
 134  222
             TransitionTarget tt = (TransitionTarget) i.next();
 135  222
             TransitionTarget parent = null;
 136  435
             while ((parent = tt.getParent()) != null) {
 137  213
                 HashSet cnt = (HashSet) counts.get(parent);
 138  213
                 if (cnt == null) {
 139  188
                     cnt = new HashSet();
 140  188
                     counts.put(parent, cnt);
 141  
                 }
 142  213
                 cnt.add(tt);
 143  213
                 tt = parent;
 144  
             }
 145  
             //top-level contribution
 146  222
             scxmlCount.add(tt);
 147  
         }
 148  
         //Validate counts:
 149  209
         for (Iterator i = counts.entrySet().iterator(); i.hasNext();) {
 150  188
             Map.Entry entry = (Map.Entry) i.next();
 151  188
             TransitionTarget tt = (TransitionTarget) entry.getKey();
 152  188
             Set count = (Set) entry.getValue();
 153  188
             if (tt instanceof Parallel) {
 154  11
                 Parallel p = (Parallel) tt;
 155  11
                 if (count.size() < p.getStates().size()) {
 156  1
                     errRep.onError(ErrorReporter.ILLEGAL_CONFIG,
 157  
                         "Not all AND states active for parallel "
 158  
                         + p.getId(), entry);
 159  1
                     legalConfig = false;
 160  
                 }
 161  
             } else {
 162  177
                 if (count.size() > 1) {
 163  1
                     errRep.onError(ErrorReporter.ILLEGAL_CONFIG,
 164  
                         "Multiple OR states active for state "
 165  
                         + tt.getId(), entry);
 166  1
                     legalConfig = false;
 167  
                 }
 168  
             }
 169  188
             count.clear(); //cleanup
 170  
         }
 171  209
         if (scxmlCount.size() > 1) {
 172  1
             errRep.onError(ErrorReporter.ILLEGAL_CONFIG,
 173  
                     "Multiple top-level OR states active!", scxmlCount);
 174  
         }
 175  
         //cleanup
 176  209
         scxmlCount.clear();
 177  209
         counts.clear();
 178  209
         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  84
         if (tt1 == tt2) {
 192  1
             return tt1; //self-transition
 193  83
         } else if (isDescendant(tt1, tt2)) {
 194  2
             return tt2;
 195  81
         } else if (isDescendant(tt2, tt1)) {
 196  2
             return tt1;
 197  
         }
 198  79
         Set parents = new HashSet();
 199  79
         TransitionTarget tmp = tt1;
 200  133
         while ((tmp = tmp.getParent()) != null) {
 201  54
             if (tmp instanceof State) {
 202  46
                 parents.add(tmp);
 203  
             }
 204  
         }
 205  79
         tmp = tt2;
 206  86
         while ((tmp = tmp.getParent()) != null) {
 207  41
             if (tmp instanceof State) {
 208  
                 //test redundant add = common ancestor
 209  37
                 if (!parents.add(tmp)) {
 210  34
                     parents.clear();
 211  34
                     return tmp;
 212  
                 }
 213  
             }
 214  
         }
 215  45
         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  89
         Set allStates = new HashSet();
 235  89
         Path p = t.getPath();
 236  
         //the easy part
 237  89
         allStates.addAll(p.getUpwardSegment());
 238  89
         TransitionTarget source = t.getParent();
 239  89
         for (Iterator act = currentStates.iterator(); act.hasNext();) {
 240  93
             TransitionTarget a = (TransitionTarget) act.next();
 241  93
             if (isDescendant(a, source)) {
 242  8
                 boolean added = false;
 243  8
                 added = allStates.add(a);
 244  20
                 while (added && a != source) {
 245  12
                     a = a.getParent();
 246  12
                     added = allStates.add(a);
 247  
                 }
 248  
             }
 249  
         }
 250  89
         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  89
         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  5
         if (child == null || parent == null) {
 308  0
             return false;
 309  
         }
 310  6
         for (Class current = child; current != Object.class;
 311  7
                 current = current.getSuperclass()) {
 312  11
             if (current == parent) {
 313  4
                 return true;
 314  
             }
 315  
         }
 316  1
         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  20
         if (clas == null || interfayce == null || !interfayce.isInterface()) {
 329  0
             return false;
 330  
         }
 331  20
         for (Class current = clas; current != Object.class;
 332  40
                 current = current.getSuperclass()) {
 333  40
             Class[] implementedInterfaces = current.getInterfaces();
 334  60
             for (int i = 0; i < implementedInterfaces.length; i++) {
 335  20
                 if (implementedInterfaces[i] == interfayce) {
 336  0
                     return true;
 337  
                 }
 338  
             }
 339  
         }
 340  20
         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  6
         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  6
                 if (node.hasChildNodes()) {
 357  6
                     Node child = node.getFirstChild();
 358  12
                     while (child != null) {
 359  6
                         if (child.getNodeType() == Node.TEXT_NODE) {
 360  6
                             node.removeChild(child);
 361  
                         }
 362  6
                         child = child.getNextSibling();
 363  
                     }
 364  
                 }
 365  
                 //create a new text node and append
 366  6
                 Text txt = node.getOwnerDocument().createTextNode(value);
 367  6
                 node.appendChild(txt);
 368  6
                 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  6
     }
 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  16
         String result = "";
 389  16
         if (node == null) {
 390  0
             return result;
 391  
         }
 392  16
         switch(node.getNodeType()) {
 393  
             case Node.ATTRIBUTE_NODE:
 394  0
                 result = node.getNodeValue();
 395  0
                 break;
 396  
             case Node.ELEMENT_NODE:
 397  16
                 if (node.hasChildNodes()) {
 398  16
                     Node child = node.getFirstChild();
 399  16
                     StringBuffer buf = new StringBuffer();
 400  32
                     while (child != null) {
 401  16
                         if (child.getNodeType() == Node.TEXT_NODE) {
 402  16
                             buf.append(((CharacterData) child).getData());
 403  
                         }
 404  16
                         child = child.getNextSibling();
 405  
                     }
 406  16
                     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  16
         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  188
         if (datamodel == null) {
 434  179
             return;
 435  
         }
 436  9
         List data = datamodel.getData();
 437  9
         if (data == null) {
 438  0
             return;
 439  
         }
 440  9
         for (Iterator iter = data.iterator(); iter.hasNext();) {
 441  14
             Data datum = (Data) iter.next();
 442  14
             Node datumNode = datum.getNode();
 443  14
             Node valueNode = null;
 444  14
             if (datumNode != null) {
 445  10
                 valueNode = datumNode.cloneNode(true);
 446  
             }
 447  
             // prefer "src" over "expr" over "inline"
 448  14
             if (!SCXMLHelper.isStringEmpty(datum.getSrc())) {
 449  0
                 ctx.setLocal(datum.getName(), valueNode);
 450  14
             } else if (!SCXMLHelper.isStringEmpty(datum.
 451  
                     getExpr())) {
 452  4
                 Object value = null;
 453  
                 try {
 454  4
                     value = evaluator.eval(ctx, datum.getExpr());
 455  0
                 } catch (SCXMLExpressionException see) {
 456  0
                     if (log != null) {
 457  0
                         log.error(see.getMessage(), see);
 458  
                     } else {
 459  0
                         Log defaultLog = LogFactory.getLog(SCXMLHelper.class);
 460  0
                         defaultLog.error(see.getMessage(), see);
 461  
                     }
 462  4
                 }
 463  4
                 ctx.setLocal(datum.getName(), value);
 464  
             } else {
 465  10
                 ctx.setLocal(datum.getName(), valueNode);
 466  
             }
 467  
         }
 468  9
     }
 469  
 
 470  
     /**
 471  
      * Discourage instantiation since this is a utility class.
 472  
      */
 473  
     private SCXMLHelper() {
 474  0
         super();
 475  0
     }
 476  
 
 477  
 }
 478