Initial import
[jpf-core.git] / src / main / gov / nasa / jpf / util / event / Event.java
diff --git a/src/main/gov/nasa/jpf/util/event/Event.java b/src/main/gov/nasa/jpf/util/event/Event.java
new file mode 100644 (file)
index 0000000..6ed18cd
--- /dev/null
@@ -0,0 +1,651 @@
+/*
+ * Copyright (C) 2014, United States Government, as represented by the
+ * Administrator of the National Aeronautics and Space Administration.
+ * All rights reserved.
+ *
+ * The Java Pathfinder core (jpf-core) platform is licensed under the
+ * Apache License, Version 2.0 (the "License"); you may not use this file except
+ * in compliance with the License. You may obtain a copy of the License at
+ * 
+ *        http://www.apache.org/licenses/LICENSE-2.0. 
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and 
+ * limitations under the License.
+ */
+
+package gov.nasa.jpf.util.event;
+
+import gov.nasa.jpf.util.Misc;
+import gov.nasa.jpf.util.OATHash;
+import java.io.PrintStream;
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * class that represents an external stimulus to the SUT, which is produced by EventTree instances
+ * (our environment models)
+ * 
+ * Note that albeit concrete EventTree can provide their own, specialized Event types, this class
+ * is generic enough that we don't declare it as abstract
+ */
+public class Event implements Cloneable {
+
+  static final Object[] NO_ARGUMENTS = new Object[0];
+  
+  //--- linkage
+  protected Event next;
+  protected Event prev;
+  protected Event alt;
+
+  //--- payload
+  protected String name;
+  protected Object[] arguments;
+  
+  protected Object source;  // optional, set on demand to keep track of where an event came from
+
+  public Event (String name){
+    this( name, NO_ARGUMENTS, null);
+  }
+
+  public Event (String name, Object source){
+    this( name, NO_ARGUMENTS, source);
+  }  
+  
+  public Event(String name, Object[] arguments) {
+    this(name, arguments, null);
+  }
+  
+  public Event(String name, Object[] arguments, Object source) {
+    this.name = name;
+    this.arguments = arguments != null ? arguments : NO_ARGUMENTS;
+    this.source = source;
+  }
+
+  
+  
+  @Override
+  public boolean equals (Object o){
+    if (o instanceof Event){
+      Event other = (Event)o;
+      
+      if (name.equals(other.name)){
+        return Misc.equals(arguments, other.arguments);
+      }
+    }
+    
+    return false;
+  }
+  
+  @Override
+  public int hashCode(){
+    int h = name.hashCode();
+    
+    for (int i=0; i<arguments.length; i++){
+      h = OATHash.hashMixin(h, arguments[i].hashCode());
+    }
+    
+    return OATHash.hashFinalize(h);
+  }
+  
+  protected void setNext (Event e){
+    next = e;
+    e.prev = this;
+  }
+
+  protected void setPrev (Event e){
+    prev = e;
+
+    if (alt != null){
+      alt.setPrev(e);
+    }
+  }
+
+  protected void setAlt (Event e){
+    alt = e;
+
+    if (prev != null) {
+      e.setPrev(prev);
+    }
+  }
+  
+  public void setLinksFrom (Event other){
+    prev = other.prev;
+    next = other.next;
+    alt = other.alt;
+  }
+
+  public Event replaceWithSequenceFrom (List<Event> list){
+    Event eLast = null;
+    
+    for (Event e: list){
+      if (eLast == null){
+        e.prev = prev;
+        e.alt = alt;
+      } else {
+        e.prev = eLast;
+        eLast.next = e;
+      }
+      
+      eLast = e;
+    }
+    
+    if (eLast != null){
+      eLast.next = next;
+      return list.get(0);
+    } else {
+      return null;
+    }
+  }
+  
+  public Event replaceWithAlternativesFrom (List<Event> list){
+    Event eLast = null;
+    for (Event e: list){
+      e.prev = prev;
+      e.next = next;
+      
+      if (eLast != null){
+        eLast.alt = e;
+      }
+      
+      eLast = e;
+    }
+    
+    if (eLast != null){
+      eLast.alt = alt;
+      return list.get(0);
+    } else {
+      return null;
+    }
+  }
+
+  public Event replaceWith (Event e){
+    e.prev = prev;
+    e.next = next;
+    e.alt = alt;
+    
+    return e;
+  }
+  
+  protected void setSource (Object source){
+    this.source = source;
+  }
+  
+  public int getNumberOfAlternatives(){
+    int n = 0;
+    for (Event e = alt; e != null; e = e.alt) {
+      n++;
+    }
+
+    return n;
+  }
+
+  public boolean hasAlternatives(){
+    return (alt != null);
+  }
+  
+  public List<Event> getAlternatives(){
+    List<Event> list = new ArrayList<Event>();
+    list.add(this);
+    for (Event e = alt; e != null; e = e.alt) {
+      list.add(e);
+    }
+    return list;
+  }
+  
+  
+  public Event unlinkedClone(){
+    try {
+      Event e = (Event)super.clone();
+      e.next = e.prev = e.alt = null;
+      return e;
+
+    } catch (CloneNotSupportedException x) {
+      throw new RuntimeException("event clone failed", x);
+    }
+    
+  }
+
+  public Event clone(){
+    try {
+      return (Event) super.clone();
+    } catch (CloneNotSupportedException cnsx){
+      throw new RuntimeException("Event clone failed");
+    }
+  }
+
+  public Event deepClone(){
+    try {
+      Event e = (Event)super.clone();
+
+      if (next != null) {
+        e.next = next.deepClone();
+        e.next.prev = e;
+
+        if (next.alt != null){
+          e.next.alt.prev = e;
+        }
+      }
+
+      if (alt != null) {
+        e.alt = alt.deepClone();
+      }
+
+      return e;
+
+    } catch (CloneNotSupportedException x) {
+      throw new RuntimeException("event clone failed", x);
+    }
+  }
+
+  public String getName(){
+    return name;
+  }
+
+  public Object[] getArguments(){
+    return arguments;
+  }
+
+  public Object getArgument(int idx){
+    if (idx < arguments.length){
+      return arguments[idx];
+    }
+    return null;
+  }
+  
+  public Event getNext(){
+    return next;
+  }
+  
+  public Event getAlt(){
+    return alt;
+  }
+  
+  public Event getPrev(){
+    return prev;
+  }
+  
+  public Object getSource(){
+    return source;
+  }
+  
+  public Event addNext (Event e){
+    boolean first = true;
+    for (Event ee : endEvents()){  // this includes alternatives
+      if (!first){
+        e = e.deepClone();
+      } else {
+        first = false;      // first one doesn't need a clone
+      }
+      ee.setNext(e);
+      e.setPrev(ee);
+    }
+
+    return this;
+  }
+
+  public Event addAlternative (Event e){
+    Event ea ;
+    for (ea = this; ea.alt != null; ea = ea.alt);
+    ea.setAlt(e);
+
+    if (next != null){
+      e.setNext( next.deepClone());
+    }
+
+    return this;
+  }
+  
+  protected static Event createClonedSequence (int firstIdx, int len, Event[] events){
+    Event base = events[firstIdx].unlinkedClone();
+    Event e = base;
+
+    for (int i = firstIdx+1; i < len; i++) {
+      Event ne = events[i].unlinkedClone();
+      e.setNext( ne);
+      e = ne;
+    }
+    
+    return base;
+  }
+  
+  /**
+   * extend this tree with a new path 
+   */
+  public void addPath (int pathLength, Event... path){
+    Event t = this;
+    Event pe;
+    
+    outer:
+    for (int i=0; i<pathLength; i++){
+      pe = path[i];
+      for (Event te = t; te != null; te = te.alt){
+        if (pe.equals(te)){      // prefix is in tree
+          
+          if (te.next == null){  // reached tree leaf
+            if (++i < pathLength){ // but the path still has events
+              Event tail = createClonedSequence( i, pathLength, path);
+              te.setNext(tail);
+              tail.setAlt( new NoEvent()); // preserve the tree path
+            }
+            return;
+            
+          } else { // there is a next in the tree
+            t = te.next;
+            
+            if (i == pathLength-1){ // but the path is done, add a NoEvent as a next alternative to mark the end
+              Event e = t.getLastAlt();
+              e.setAlt(new NoEvent());
+              return;
+              
+            } else {
+              continue outer;
+            }
+          }
+        }
+      }
+      
+      //--- path prefix was not in tree, append as (last) alternative
+      Event tail = createClonedSequence( i, pathLength, path);
+      Event e = t.getLastAlt();
+      e.setAlt( tail);
+      
+      return;
+    }
+  }
+
+  public Event getLastAlt (){
+    Event e;
+    for (e=this; e.alt != null; e = e.alt);
+    return e;
+  }
+  
+  protected void collectEndEvents (List<Event> list, boolean includeNoEvents) {
+    if (next != null) {
+      next.collectEndEvents(list, includeNoEvents);
+      
+    } else { // base case: no next
+      // strip trailing NoEvents 
+      if (prev == null){
+        list.add(this); // root NoEvents have to stay
+        
+      } else { // else we skip trailing NoEvents up to root
+        Event ee = this;
+        if (!includeNoEvents){
+          for (Event e=this; e.prev != null && (e instanceof NoEvent); e = e.prev){
+            ee = e.prev;
+          }
+        }
+        list.add(ee);
+      }
+    }
+
+    if (alt != null) {
+      alt.collectEndEvents(list, includeNoEvents);
+    }
+  }
+
+  public Event endEvent() {
+    if (next != null) {
+      return next.endEvent();
+    } else {
+      return this;
+    }
+  }
+
+  public List<Event> visibleEndEvents(){
+    List<Event> list = new ArrayList<Event>();
+    collectEndEvents(list, false);
+    return list;
+  }
+  
+  public List<Event> endEvents(){
+    List<Event> list = new ArrayList<Event>();
+    collectEndEvents(list, true);
+    return list;
+  }
+  
+  private void interleave (Event a, Event b, Event[] path, int pathLength, int i, Event result){
+    if (a == null && b == null){ // base case
+      result.addPath(pathLength, path);
+      
+    } else {
+      if (a != null) {
+        path[i] = a;
+        interleave(a.prev, b, path, pathLength, i - 1, result);
+      }
+
+      if (b != null) {
+        path[i] = b;
+        interleave(a, b.prev, path, pathLength, i - 1, result);
+      }
+    }
+  }
+  
+  /**
+   * this creates a new tree that contains all paths resulting from
+   * all interleavings of all paths of this tree with the specified other events
+   * 
+   * BEWARE: this is a combinatorial bomb that should only be used if we know all
+   * paths are short
+   */
+  public Event interleave (Event... otherEvents){
+    Event t = new NoEvent(); // we need a root for the new tree
+    
+    Event[] pathBuffer = new Event[32];
+    int mergedTrees = 0;
+    
+    for (Event o : otherEvents){
+      List<Event> endEvents = (mergedTrees++ > 0) ? t.visibleEndEvents() : visibleEndEvents();
+
+      for (Event ee1 : endEvents) {
+        for (Event ee2 : o.visibleEndEvents()) {
+          int n = ee1.getPathLength() + ee2.getPathLength();
+          if (n > pathBuffer.length){
+            pathBuffer = new Event[n];
+          }
+
+          interleave(ee1, ee2, pathBuffer, n, n - 1, t);
+        }
+      }
+    }
+        
+    return t.alt;
+  }
+  
+  
+  
+  private void removeSource (Object src, Event[] path, int i, Event result){
+    
+    if (alt != null){
+      alt.removeSource(src, path, i, result);
+    }
+    
+    if (source != src){
+      path[i++] = this;
+    }
+    
+    if (next != null){
+      next.removeSource(src, path, i, result);
+      
+    } else { // done, add path to result
+      result.addPath( i, path);
+    }
+  }
+  
+  /**
+   * remove all events from this tree that are from the specified source 
+   */
+  public Event removeSource (Object src){
+    Event base = new NoEvent(); // we need a root to add to
+    int maxDepth = getMaxDepth();
+    Event[] pathBuffer = new Event[maxDepth];
+    
+    removeSource( src, pathBuffer, 0, base);
+    
+    return base.alt;
+  }
+  
+  private void printPath (PrintStream ps, boolean isLast){
+    if (prev != null){
+      prev.printPath(ps, false);
+    }
+    
+    if (!isNoEvent()){
+      ps.print(name);
+      if (!isLast){
+        ps.print(',');
+      }
+    }
+  }
+  
+  public void printPath (PrintStream ps){
+    printPath(ps, true);
+  }
+
+  @Override
+  public String toString(){
+    StringBuilder sb = new StringBuilder();
+    sb.append(name);
+    if (arguments != NO_ARGUMENTS) {
+      sb.append('(');
+      boolean first = true;
+      for (Object a : arguments) {
+        if (first){
+          first = false;
+        } else {
+          sb.append(',');
+        }
+        sb.append(a.toString());
+      }
+      sb.append(')');
+    }
+    return sb.toString();
+  }
+
+  
+  /**
+   * upwards path length 
+   */
+  public int getPathLength(){
+    int n=0;
+    
+    for (Event e=this; e != null; e = e.prev){
+      n++;
+    }
+    
+    return n;
+  }
+  
+  
+  private int getMaxDepth (int depth){
+    int maxAlt = depth;
+    int maxNext = depth;
+    
+    if (alt != null){
+      maxAlt = alt.getMaxDepth(depth);
+    }
+    
+    if (next != null){
+      maxNext = next.getMaxDepth(depth + 1);
+    }
+    
+    if (maxAlt > maxNext){
+      return maxAlt;
+    } else {
+      return maxNext;
+    }
+  }
+  
+  /**
+   * maximum downwards tree depth 
+   */
+  public int getMaxDepth(){
+    return getMaxDepth(1);
+  }
+  
+  public Event[] getPath(){
+    int n = getPathLength();
+    Event[] trace = new Event[n];
+    
+    for (Event e=this; e != null; e = e.prev){
+      trace[--n] = e;
+    }
+    
+    return trace;
+  }
+  
+  public void printTree (PrintStream ps, int level) {
+    for (int i = 0; i < level; i++) {
+      ps.print(". ");
+    }
+    
+    ps.print(this);
+    //ps.print(" [" + prev + ']');
+    ps.println();
+
+    if (next != null) {
+      next.printTree(ps, level + 1);
+    }
+
+    if (alt != null) {
+      alt.printTree(ps, level);
+    }
+  }
+  
+  public boolean isEndOfTrace (String[] eventNames){
+    int n = eventNames.length-1;
+    
+    for (Event e=this; e!= null; e = e.prev){
+      if (e.getName().equals(eventNames[n])){
+        n--;
+      } else {
+        return false;
+      }
+    }
+    
+    return (n == 0);
+  }
+  
+  protected void collectTrace (StringBuilder sb, String separator, boolean isLast){
+    if (prev != null){
+      prev.collectTrace(sb, separator, false);    
+    }
+
+    if (!isNoEvent()){
+      sb.append(toString());
+      
+      if (!isLast && separator != null){
+        sb.append(separator);        
+      }
+    }
+  }
+  
+  public String getPathString (String separator){
+    StringBuilder sb = new StringBuilder();
+    collectTrace( sb, separator, true);
+    return sb.toString();
+  }
+  
+  public boolean isNoEvent(){
+    return false;
+  }
+
+  public boolean isSystemEvent(){
+    return false;
+  }
+
+  //--- generic processing interface
+  
+  public void process(Object source){
+    // can be overridden by subclass if instance has sufficient context info
+  }
+  
+  public void setProcessed(){
+    // can be overridden by subclass, e.g. to maintain event caches
+  }
+}