[sikuli] 319/385: completely revising observe (ongoing)

Gilles Filippini pini at moszumanska.debian.org
Sun Jun 29 19:26:28 UTC 2014


This is an automated email from the git hooks/post-receive script.

pini pushed a commit to tag upstream/1.1.0_beta1
in repository sikuli.

commit 68b3354b5507275b2617b71bb07238303da40b3b
Author: Raimund Hocke <rmhdevelop at me.com>
Date:   Mon Mar 3 08:33:30 2014 +0100

    completely revising observe (ongoing)
---
 .../main/java/org/sikuli/script/ObserveAppear.java |   4 +-
 .../main/java/org/sikuli/script/ObserveChange.java |   4 +-
 .../main/java/org/sikuli/script/ObserveEvent.java  |  38 +-
 .../main/java/org/sikuli/script/ObserveVanish.java |   4 +-
 API/src/main/java/org/sikuli/script/Observer.java  | 239 +++++++------
 .../java/org/sikuli/script/ObserverCallBack.java   |   2 +-
 API/src/main/java/org/sikuli/script/Observing.java | 397 ++++++---------------
 API/src/main/java/org/sikuli/script/Region.java    | 106 ++++--
 API/src/main/java/org/sikuli/script/SikuliX.java   |  26 +-
 9 files changed, 350 insertions(+), 470 deletions(-)

diff --git a/API/src/main/java/org/sikuli/script/ObserveAppear.java b/API/src/main/java/org/sikuli/script/ObserveAppear.java
index 6ff50a6..b6f8a5d 100755
--- a/API/src/main/java/org/sikuli/script/ObserveAppear.java
+++ b/API/src/main/java/org/sikuli/script/ObserveAppear.java
@@ -11,8 +11,8 @@ package org.sikuli.script;
  */
 public class ObserveAppear extends ObserveEvent {
 
-   public ObserveAppear(Object ptn, Match m, Region r){
-      super(ptn, m, r);
+   public ObserveAppear(String name, Object ptn, Match m, Region r){
+      super(name, ptn, m, r);
       type = Type.APPEAR;
    }
 
diff --git a/API/src/main/java/org/sikuli/script/ObserveChange.java b/API/src/main/java/org/sikuli/script/ObserveChange.java
index 0c2ca74..812f654 100755
--- a/API/src/main/java/org/sikuli/script/ObserveChange.java
+++ b/API/src/main/java/org/sikuli/script/ObserveChange.java
@@ -12,10 +12,10 @@ import java.util.List;
  * INTERNAL USE
  */
 public class ObserveChange extends ObserveEvent {
-   public ObserveChange(List<Match> results, Region r, int eventIndex){
+   public ObserveChange(String name, List<Match> results, Region r, int eventIndex){
+     super(name, null, null, r);
       type = Type.CHANGE;
       setChanges(results);
-      setRegion(r);
       setIndex(eventIndex);
    }
 
diff --git a/API/src/main/java/org/sikuli/script/ObserveEvent.java b/API/src/main/java/org/sikuli/script/ObserveEvent.java
index 1713b4b..9b8e6a6 100755
--- a/API/src/main/java/org/sikuli/script/ObserveEvent.java
+++ b/API/src/main/java/org/sikuli/script/ObserveEvent.java
@@ -7,6 +7,7 @@
 package org.sikuli.script;
 
 import java.util.ArrayList;
+import java.util.Date;
 import java.util.List;
 
 public class ObserveEvent {
@@ -25,22 +26,34 @@ public class ObserveEvent {
   private Match match = null;
   private int index = -1;
   private List<Match> changes = null;
+  private long time;
+  private String name;
 
   public ObserveEvent() {
   }
-
+  
   /**
    * INTERNAL USE ONLY: creates an observed event
    */
-  public ObserveEvent(Object ptn, Match m, Region r) {
-		init(ptn, m, r);
+  public ObserveEvent(String name, Object ptn, Match m, Region r) {
+    init(name, ptn, m, r);
   }
 
-	private void init(Object ptn, Match m, Region r) {
+	private void init(String name, Object ptn, Match m, Region r) {
+    this.name = name;
     setRegion(r);
     setMatch(m);
     setPattern(ptn);
+    time = new Date().getTime();
 	}
+  
+  /**
+   *
+   * @return the observer name of this event
+   */
+  public String getName() {
+    return name;
+  }
 
   /**
    *
@@ -134,17 +147,20 @@ public class ObserveEvent {
    * @param secs
    */
   public void repeat(long secs) {
-    region.getObserver().repeat(type, pattern, match, secs);
+    region.getObserver().repeat(type, name, match, secs);
   }
 
   /**
    * @return the number how often this event has already been triggered until now
    */
   public int getCount() {
+    if (region.getObserver() == null) {
+      return 1;
+    }
     if (type == Type.CHANGE) {
-      return region.getObserver().getChangedCount(index);
+      return region.getObserver().getChangedCount(name);
     } else {
-      return region.getEvtMgr().getCount(pattern);
+      return region.getObserver().getCount(name);
     }
   }
 
@@ -162,11 +178,11 @@ public class ObserveEvent {
   @Override
   public String toString() {
     if (type == Type.CHANGE) {
-      return String.format("Event(%s) on: %s with: %d count: %d", 
-            type, region, index, getCount());
+      return String.format("Event(%s) %s on: %s with: %d count: %d", 
+            type, name, region, index, getCount());
     } else {
-      return String.format("Event(%s) on: %s with: %s match: %s count: %d",
-            type, region, pattern, match, getCount());
+      return String.format("Event(%s) %s on: %s with: %s match: %s count: %d",
+            type, name, region, pattern, match, getCount());
     }
   }
 }
diff --git a/API/src/main/java/org/sikuli/script/ObserveVanish.java b/API/src/main/java/org/sikuli/script/ObserveVanish.java
index 2ed39aa..8a86805 100755
--- a/API/src/main/java/org/sikuli/script/ObserveVanish.java
+++ b/API/src/main/java/org/sikuli/script/ObserveVanish.java
@@ -10,8 +10,8 @@ package org.sikuli.script;
  * INTERNAL USE
  */
 public class ObserveVanish extends ObserveEvent {
-   public ObserveVanish(Object ptn, Match m, Region r){
-      super(ptn, m, r);
+   public ObserveVanish(String name, Object ptn, Match m, Region r){
+      super(name, ptn, m, r);
       type = Type.VANISH;
    }
 }
diff --git a/API/src/main/java/org/sikuli/script/Observer.java b/API/src/main/java/org/sikuli/script/Observer.java
index ed907ad..65bfab5 100755
--- a/API/src/main/java/org/sikuli/script/Observer.java
+++ b/API/src/main/java/org/sikuli/script/Observer.java
@@ -6,10 +6,14 @@
  */
 package org.sikuli.script;
 
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
 import org.sikuli.basics.Settings;
 import org.sikuli.basics.Debug;
-import java.awt.AWTException;
-import java.util.*;
 import org.sikuli.natives.FindInput;
 import org.sikuli.natives.FindResult;
 import org.sikuli.natives.FindResults;
@@ -35,58 +39,67 @@ public class Observer {
   private Region observedRegion;
   private Mat lastImgMat = null;
   private org.opencv.core.Mat lastImageMat = null;
-  private Map<Object, State> eventStates;
-  private Map<Object, Long> repeatWaitTimes;
-  private Map<Object, Integer> happenedCount;
-  private Map<Object, Match> lastMatches;
-  private Map<Object, String> eventNames;
-  private Map<Object, Object> appearCallBacks, vanishCallBacks;
-  private Map<Integer, Object> changeCallBacks;
-  private Map<Integer, Integer> changedCount;
-  private Map<Integer, String> onChangeNames;
+  private final Map<String, State> eventStates;
+  private final Map<String, Long> repeatWaitTimes;
+  private final Map<String, Match> lastMatches;
+  private final Map<String, Object> eventNames;
+  private final Map<String, ObserveEvent.Type> eventTypes;
+  private final Map<String, Object> eventCallBacks;
+  private final Map<String, Integer> happenedCount;
+  private final Map<String, Integer> onChangeNames;
   private int minChanges;
   private boolean sthgLeft;
   private boolean shouldCheckChanges;
 
   public Observer(Region region) {
     observedRegion = region;
-    eventStates = new HashMap<Object, State>();
-    repeatWaitTimes = new HashMap<Object, Long>();
-    happenedCount = new HashMap<Object, Integer>();
-    lastMatches = new HashMap<Object, Match>();
-    eventNames = new HashMap<Object, String>();
-    appearCallBacks = new HashMap<Object, Object>();
-    vanishCallBacks = new HashMap<Object, Object>();
-    changeCallBacks = new HashMap<Integer, Object>();
-    changedCount = new HashMap<Integer, Integer>();
-    onChangeNames = new HashMap<Integer, String>();
+    eventStates = Collections.synchronizedMap(new HashMap<String, State>());
+    repeatWaitTimes = Collections.synchronizedMap(new HashMap<String, Long>());
+    happenedCount = Collections.synchronizedMap(new HashMap<String, Integer>());
+    lastMatches = Collections.synchronizedMap(new HashMap<String, Match>());
+    eventNames = Collections.synchronizedMap(new HashMap<String, Object>());
+    eventTypes = Collections.synchronizedMap(new HashMap<String, ObserveEvent.Type>());
+    eventCallBacks = Collections.synchronizedMap(new HashMap<String, Object>());
+    onChangeNames = Collections.synchronizedMap(new HashMap<String, Integer>());
   }
 
   public void initialize() {
     log(3, "resetting observe states for " + observedRegion.toStringShort());
     sthgLeft = true;
     shouldCheckChanges = true;
-    for (Object ptn : eventStates.keySet()) {
-      eventStates.put(ptn, State.FIRST);
-      happenedCount.put(ptn, 0);
+    for (String name : eventNames.keySet()) {
+      eventStates.put(name, State.FIRST);
+      happenedCount.put(name, 0);
     }
-    for (int n : changeCallBacks.keySet()) {
-      changedCount.put(n, 0);
+    for (String name : onChangeNames.keySet()) {
+      happenedCount.put(name, 0);
     }
   }
 
+  public String[] getNames() {
+    String[] names = new String[eventNames.size() + onChangeNames.size()];
+    int i = 0;
+    for (String n : eventNames.keySet()) {
+      names[i++] = n;
+    }
+    for (String n : onChangeNames.keySet()) {
+      names[i++] = n;
+    }
+    return names;
+  }
+
   public void setRegion(Region reg) {
     observedRegion = reg;
   }
 
-  public int getCount(Object ptn) {
-    return happenedCount.get(ptn);
+  public int getCount(String name) {
+    return happenedCount.get(name);
   }
 
-  public int getChangedCount(int index) {
-    return changedCount.get(index);
+  public int getChangedCount(String name) {
+    return happenedCount.get(name);
   }
-  
+
   private <PSC> float getSimiliarity(PSC ptn) {
     float similarity = -1f;
     if (ptn instanceof Pattern) {
@@ -98,53 +111,54 @@ public class Observer {
     return similarity;
   }
 
-  public <PSC> void addAppearObserver(PSC ptn, ObserverCallBack ob, String name) {
-    appearCallBacks.put(ptn, ob);
-    eventStates.put(ptn, State.FIRST);
-    eventNames.put(ptn, name);
-  }
-
-  public <PSC> void removeAppearObserver(PSC ptn) {
-    Observing.remove(eventNames.get(ptn));
-    eventNames.remove(ptn);
-    appearCallBacks.remove(ptn);
-    eventStates.remove(ptn);
-  }
-
-  public <PSC> void addVanishObserver(PSC ptn, ObserverCallBack ob, String name) {
-    vanishCallBacks.put(ptn, ob);
-    eventStates.put(ptn, State.FIRST);
-    eventNames.put(ptn, name);
+  public <PSC> void addObserver(PSC ptn, ObserverCallBack ob, String name, ObserveEvent.Type type) {
+    eventCallBacks.put(name, ob);
+    eventStates.put(name, State.FIRST);
+    eventNames.put(name, ptn);
   }
 
-  public <PSC> void removeVanishObserver(PSC ptn) {
-    Observing.remove(eventNames.get(ptn));
-    eventNames.remove(ptn);
-    vanishCallBacks.remove(ptn);
-    eventStates.remove(ptn);
+  public void removeObserver(String name) {
+    Observing.remove(name);
+    eventNames.remove(name);
+    eventCallBacks.remove(name);
+    eventStates.remove(name);
   }
 
-  private void callAppearObserver(Object ptn, Match m) {
-    log(lvl, "appeared: %s with: %s\nat: %s", eventNames.get(ptn), ptn, m);
-    ObserveAppear observeEvent = new ObserveAppear(ptn, m, observedRegion);
-    Object callBack = appearCallBacks.get(ptn);
-    Observing.addEvent(eventNames.get(ptn), observeEvent);
+  private void callAppearObserver(String name, Match m) {
+    Object ptn = eventNames.get(name);
+    log(lvl, "appeared: %s with: %s\nat: %s", name, ptn, m);
+    ObserveAppear observeEvent = new ObserveAppear(name, ptn, m, observedRegion);
+    Object callBack = eventCallBacks.get(name);
+    Observing.addEvent(observeEvent);
     if (callBack != null && callBack instanceof ObserverCallBack) {
       log(lvl, "running call back");
-      ((ObserverCallBack) appearCallBacks.get(ptn)).appeared(observeEvent);
+      ((ObserverCallBack) callBack).appeared(observeEvent);
     }
   }
 
-  private void callVanishObserver(Object ptn, Match m) {
-    log(lvl, "vanished: %s with: %s\nat: %s", eventNames.get(ptn), ptn, m);
-    ObserveVanish observeEvent = new ObserveVanish(ptn, m, observedRegion);
-    Object callBack = vanishCallBacks.get(ptn);
-    Observing.addEvent(eventNames.get(ptn), observeEvent);
+  private void callVanishObserver(String name, Match m) {
+    Object ptn = eventNames.get(name);
+    log(lvl, "vanished: %s with: %s\nat: %s", name, ptn, m);
+    ObserveVanish observeEvent = new ObserveVanish(name, ptn, m, observedRegion);
+    Object callBack = eventCallBacks.get(name);
+    Observing.addEvent(observeEvent);
     if (callBack != null && callBack instanceof ObserverCallBack) {
       log(lvl, "running call back");
-      ((ObserverCallBack) vanishCallBacks.get(ptn)).vanished(observeEvent);
+      ((ObserverCallBack) callBack).vanished(observeEvent);
     }
   }
+  
+  private void callEventObserver(String name, Match m) {
+    Object ptn = eventNames.get(name);
+    log(lvl, "appeared: %s with: %s\nat: %s", name, ptn, m);
+    ObserveAppear observeEvent = new ObserveAppear(name, ptn, m, observedRegion);
+    Object callBack = eventCallBacks.get(name);
+    Observing.addEvent(observeEvent);
+    if (callBack != null && callBack instanceof ObserverCallBack) {
+      log(lvl, "running call back");
+      ((ObserverCallBack) callBack).appeared(observeEvent);
+    }    
+  }
 
   private void checkPatterns(ScreenImage simg) {
     Finder finder = null;
@@ -156,13 +170,14 @@ public class Observer {
     }
     String imgOK;
     log(lvl + 1, "checkPatterns entry: sthgLeft: %s isObserving: %s", sthgLeft, observedRegion.isObserving());
-    for (Object ptn : eventStates.keySet()) {
-      if (eventStates.get(ptn) != State.FIRST
-              && eventStates.get(ptn) != State.UNKNOWN
-              && eventStates.get(ptn) != State.REPEAT) {
+    for (String name : eventStates.keySet()) {
+      if (eventStates.get(name) != State.FIRST
+              && eventStates.get(name) != State.UNKNOWN
+              && eventStates.get(name) != State.REPEAT) {
         continue;
       }
       imgOK = null;
+      Object ptn = eventNames.get(name);
       if (ptn instanceof String) {
         imgOK = finder.find((String) ptn);
         Image img = Image.create((String) ptn);
@@ -178,14 +193,14 @@ public class Observer {
       }
       if (null == imgOK) {
         Debug.error("EventMgr: checkPatterns: Image not valid", ptn);
-        eventStates.put(ptn, State.MISSING);
+        eventStates.put(name, State.MISSING);
         continue;
       }
-      if (eventStates.get(ptn) == State.REPEAT) {
+      if (eventStates.get(name) == State.REPEAT) {
         log(lvl, "repeat: checking");
-        if (lastMatches.get(ptn).exists(ptn) != null) {
-          if ((new Date()).getTime() > repeatWaitTimes.get(ptn)) {
-            eventStates.put(ptn, State.APPEARED);
+        if (lastMatches.get(name).exists(ptn) != null) {
+          if ((new Date()).getTime() > repeatWaitTimes.get(name)) {
+            eventStates.put(name, State.APPEARED);
             log(lvl, "repeat: vanish timeout");
             // time out
           } else {
@@ -193,7 +208,7 @@ public class Observer {
           }
           continue; // not vanished within given time or still there
         } else {
-          eventStates.put(ptn, State.UNKNOWN);
+          eventStates.put(name, State.UNKNOWN);
           sthgLeft = true;
           log(lvl, "repeat: has vanished");
           continue; // has vanished, repeat
@@ -205,7 +220,7 @@ public class Observer {
         m = finder.next();
         if (m.getScore() >= getSimiliarity(ptn)) {
           hasMatch = true;
-          lastMatches.put(ptn, m);
+          lastMatches.put(name, m);
         }
       }
       if (hasMatch) {
@@ -214,14 +229,14 @@ public class Observer {
       } else if (eventStates.get(ptn) == State.FIRST) {
         log(lvl + 1, "checkPatterns: " + ptn.toString() + " match: "
                 + "NO" + " in " + observedRegion.toStringShort());
-        eventStates.put(ptn, State.UNKNOWN);
+        eventStates.put(name, State.UNKNOWN);
       }
-      if (appearCallBacks.containsKey(ptn)) {
-        if (eventStates.get(ptn) != State.APPEARED) {
+      if (appearCallBacks.containsKey(name)) {
+        if (eventStates.get(name) != State.APPEARED) {
           if (hasMatch) {
-            eventStates.put(ptn, State.APPEARED);
-            happenedCount.put(ptn, happenedCount.get(ptn) + 1);
-            callAppearObserver(ptn, m);
+            eventStates.put(name, State.APPEARED);
+            happenedCount.put(name, happenedCount.get(name) + 1);
+            callAppearObserver(name, m);
           } else {
             sthgLeft = true;
           }
@@ -229,9 +244,9 @@ public class Observer {
       } else if (vanishCallBacks.containsKey(ptn)) {
         if (eventStates.get(ptn) != State.VANISHED) {
           if (!hasMatch) {
-            eventStates.put(ptn, State.VANISHED);
-            happenedCount.put(ptn, happenedCount.get(ptn) + 1);
-            callVanishObserver(ptn, lastMatches.get(ptn));
+            eventStates.put(name, State.VANISHED);
+            happenedCount.put(name, happenedCount.get(name) + 1);
+            callVanishObserver(name, lastMatches.get(name));
           } else {
             sthgLeft = true;
           }
@@ -244,39 +259,41 @@ public class Observer {
     log(lvl + 1, "checkPatterns exit: sthgLeft: %s isObserving: %s", sthgLeft, observedRegion.isObserving());
   }
 
-  public void repeat(ObserveEvent.Type type, Object pattern, Match match, long secs) {
+  public void repeat(ObserveEvent.Type type, String name, Match match, long secs) {
     if (type == ObserveEvent.Type.CHANGE) {
       Debug.error("EventMgr: repeat: CHANGE repeats automatically");
     } else if (type == ObserveEvent.Type.VANISH) {
       Debug.error("EventMgr: repeat: not supported for VANISH");
     } else if (type == ObserveEvent.Type.APPEAR) {
-      eventStates.put(pattern, State.REPEAT);
+      eventStates.put(name, State.REPEAT);
       if (secs <= 0) {
         secs = (long) observedRegion.getWaitForVanish();
       }
-      repeatWaitTimes.put(pattern, (new Date()).getTime() + 1000 * secs);
+      repeatWaitTimes.put(name, (new Date()).getTime() + 1000 * secs);
       log(lvl, "repeat: requested for APPEAR: "
-              + pattern.toString() + " at " + match.toStringShort() + " after " + secs + " seconds");
+              + eventNames.get(name).toString() + " at " + match.toStringShort() + " after " + secs + " seconds");
       sthgLeft = true;
     }
   }
 
   public void addChangeObserver(int threshold, ObserverCallBack ob, String name) {
-    changeCallBacks.put(new Integer(threshold), ob);
-    minChanges = getMinChanges();
-    onChangeNames.put(threshold, name);
-  }
-
-  public void removeChangeObserver(int threshold) {
-    Observing.remove(onChangeNames.get(threshold));
-    eventNames.remove(threshold);
-    changeCallBacks.remove(new Integer(threshold));
+    eventCallBacks.put(name, ob);
     minChanges = getMinChanges();
+    onChangeNames.put(name, threshold);
   }
 
+//  public void removeChangeObserver(int threshold) {
+//    Observing.remove(onChangeNames.get(threshold));
+//    onChangeNames.remove(threshold);
+//    changeCallBacks.remove(new Integer(threshold));
+//    minChanges = getMinChanges();
+//  }
+//
   private int getMinChanges() {
     int min = Integer.MAX_VALUE;
-    for (Integer n : changeCallBacks.keySet()) {
+    int n;
+    for (String name : onChangeNames.keySet()) {
+      n = onChangeNames.get(name);
       if (n < min) {
         min = n;
       }
@@ -286,9 +303,11 @@ public class Observer {
 
   private int callChangeObserver(FindResults results) {
     int activeChangeCallBacks = 0;
+    int n;
     log(lvl, "changes: %d in: %s", results.size(), observedRegion);
-    for (Integer n : changeCallBacks.keySet()) {
-      if (changedCount.get(n) == -1) {
+    for (String name : onChangeNames.keySet()) {
+      n = onChangeNames.get(name);
+      if (happenedCount.get(name) == -1) {
         continue;
       }
       activeChangeCallBacks++;
@@ -300,16 +319,16 @@ public class Observer {
         }
       }
       if (changes.size() > 0) {
-        changedCount.put(n, changedCount.get(n) + 1);
-        ObserveChange observeEvent = new ObserveChange(changes, observedRegion, n);
-        Object callBack = changeCallBacks.get(n);
-        Observing.addEvent(onChangeNames.get(n), observeEvent);
+        happenedCount.put(name, happenedCount.get(name) + 1);
+        ObserveChange observeEvent = new ObserveChange(name, changes, observedRegion, n);
+        Object callBack = eventCallBacks.get(name);
+        Observing.addEvent(observeEvent);
         if (callBack != null && callBack instanceof ObserverCallBack) {
           log(lvl, "running call back");
-          ((ObserverCallBack) changeCallBacks.get(n)).changed(observeEvent);
+          ((ObserverCallBack) callBack).changed(observeEvent);
         } else {
           // onChange only repeated if CallBack given
-          changedCount.put(n, -1);
+          happenedCount.put(name, -1);
           activeChangeCallBacks--;
         }
       }
@@ -348,7 +367,9 @@ public class Observer {
       fin.setSimilarity(minChanges);
       FindResults results = Vision.findChanges(fin);
       if (results.size() > 0) {
-        if (0 == callChangeObserver(results)) changesObserved = false;
+        if (0 == callChangeObserver(results)) {
+          changesObserved = false;
+        }
       }
       lastImgMat = target;
     }
@@ -369,7 +390,7 @@ public class Observer {
     }
     if (observedRegion.isObserving()) {
       ret = sthgLeft;
-      if (shouldCheckChanges && changeCallBacks.size() > 0) {
+      if (shouldCheckChanges && onChangeNames.size() > 0) {
         changesObserved = checkChanges(simg);
         shouldCheckChanges = changesObserved;
         if (!observedRegion.isObserving()) {
diff --git a/API/src/main/java/org/sikuli/script/ObserverCallBack.java b/API/src/main/java/org/sikuli/script/ObserverCallBack.java
index ae4362e..c328a73 100644
--- a/API/src/main/java/org/sikuli/script/ObserverCallBack.java
+++ b/API/src/main/java/org/sikuli/script/ObserverCallBack.java
@@ -37,6 +37,6 @@ public class ObserverCallBack implements EventListener {
   public void changed(ObserveEvent e) {
   }
 
-  public void happened(Observing.Event e) {
+  public void happened(ObserveEvent e) {
   }
 }
diff --git a/API/src/main/java/org/sikuli/script/Observing.java b/API/src/main/java/org/sikuli/script/Observing.java
index 8023a77..e76383c 100644
--- a/API/src/main/java/org/sikuli/script/Observing.java
+++ b/API/src/main/java/org/sikuli/script/Observing.java
@@ -9,13 +9,14 @@ package org.sikuli.script;
 import java.util.ArrayList;
 import java.util.Collections;
 import java.util.Date;
-import java.util.Iterator;
+import java.util.HashMap;
 import java.util.List;
+import java.util.Map;
 import org.sikuli.basics.Debug;
 
 /**
- * This class implements a container that globally collects
- * all running observations.<br />
+ * This class globally collects
+ * all running observations and tracks the created events.<br />
  */
 public class Observing {
 
@@ -26,241 +27,114 @@ public class Observing {
     Debug.logx(level, "", me + ": " + message, args);
   }
   
-  private static class Entry {
+  private Observing() {
+  }
+
+  private static class ObserverEntry {
 
     private Region region;
-    private String name;
     private ObserveEvent.Type type;
     private boolean isActive = true;
     private ObserverCallBack obs;
 
-    protected Entry(String name, Region reg, ObserverCallBack obs, ObserveEvent.Type type) {
-      this.name = name;
+    protected ObserverEntry(Region reg, ObserverCallBack obs, ObserveEvent.Type type) {
       region = reg;
       this.obs = obs;
       this.type = type;
     }
-    
-    protected void destroy() {
-      region = null;
-      name = null;
-      type = null;
-      obs = null;
-    }
   }
 
-  public static class Event extends ObserveEvent {
-    private Entry observer = null;
-    private long time = 0;
-    
-    protected Event() {
-    }
-    
-    protected Event(Event evt) {
-      observer = evt.observer;
-      time = evt.time;
-      setRegion(evt.getRegion());
-      setMatch(evt.getMatch());
-      setChanges(evt.getChanges());
-      setPattern(evt.getPattern());
-      setIndex(evt.getIndex());
-    }
+  private static final Map<String, ObserverEntry> observers = Collections.synchronizedMap(new HashMap<String, ObserverEntry>());
+  private static final Map<String, ObserveEvent> events = Collections.synchronizedMap(new HashMap<String, ObserveEvent>());
+  private static final List<Region> runningObservers = Collections.synchronizedList(new ArrayList<Region>());
 
-    public long getTime() {
-      return time;
-    }
-    
-    protected void destroy() {
-      observer = null;
-      time = 0;
-    }
+  protected static void addRunningObserver(Region r) {
+    runningObservers.add(r);
+    log(lvl,"add observer: now running %d observer(s)", runningObservers.size());
   }
 
-  private static List<Entry> observers = Collections.synchronizedList(new ArrayList<Entry>());
-  private static List<Event> events = Collections.synchronizedList(new ArrayList<Event>());
-
-  /**
-   * adds an observer with a callback to the list
-   *
-   * @param reg the observed region
-   * @param obs the callback
-   * @param type one off ObserveEvent.Type.APPEAR, VANISH, CHANGE, GENERIC
-   * @return a unique name derived from time or null if not possible
-   */
-  public static synchronized String add(Region reg, ObserverCallBack obs, ObserveEvent.Type type) {
-    String name = createName();
-    if (add(name, reg, obs, type)) {
-      return name;
-    }
-    return null;
+  protected static void removeRunningObserver(Region r) {
+    runningObservers.remove(r);
+    log(lvl, "remove observer: now running %d observer(s)", runningObservers.size());
   }
-
-  /**
-   * adds an observer to the list having no callback
-   *
-   * @param reg the observed region
-   * @param name a unique name
-   * @param type one off Observing.Type.APPEAR, VANISH, CHANGE, GENERIC
-   * @return the observers name or null if not possible (duplicate?)
-   */
-  public static synchronized String add(Region reg, String name, ObserveEvent.Type type) {
-    if (add(name, reg, null, type)) {
-      return name;
+  
+  protected static void stopRunningObservers() {
+    if (runningObservers.size() > 0) {
+      log(lvl, "stopping %d running observer(s)", runningObservers.size());
+      synchronized (runningObservers) {
+        for (Region r : runningObservers) {
+          r.stopObserver();
+        }
+        runningObservers.clear();
+      }
     }
-    return null;
+    Observing.clear();
   }
 
   /**
-   * adds an observer of type GENERIC to the list having no callback
+   * INTERNAL USE: adds an observer to the list
    *
-   * @param name a unique name
-   * @return the observers name or null if not possible (duplicate?)
+   * @param reg the observed region
+   * @param obs the callback (might be null - observer without call back)
+   * @param type one off ObserveEvent.Type.APPEAR, VANISH, CHANGE, GENERIC
+   * @param target 
+   * @return a unique name derived from time or null if not possible
    */
-  public static synchronized String add(String name) {
-    if (add(name, null, null, ObserveEvent.Type.GENERIC)) {
-      return name;
-    }
-    return null;
-  }
-
-  private static boolean add(String name, Region reg, ObserverCallBack obs, ObserveEvent.Type type) {
-    if (hasName(name, reg)) {
-      return false;
-    }
-    Iterator<Entry> iter = observers.iterator();
-    while (iter.hasNext()) {
-      if (iter.next() == null) {
-        iter.remove();
-      }
-    }
-    return observers.add(new Entry(name, reg, obs, type));
-  }
-
-  private static String createName() {
+  public static String add(Region reg, ObserverCallBack obs, ObserveEvent.Type type, Object target) {
     String name = null;
-    while (null == name) {
-      name = "" + new Date().getTime();
-      if (!hasName(name, null)) {
-        return name;
-      } else {
-        name = null;
+    long now = new Date().getTime();
+    while (true) {
+      name = "" + now++;
+      if (!hasName(name)) {
+        break;
       }
-      try {
-        Thread.sleep(5);
-      } catch(Exception ex) {}
     }
-    return null;
+    observers.put("" + now, new ObserverEntry(reg, obs, type));
+    reg.getObserver().addObserver(target, (ObserverCallBack) obs, name, type);
+    return name;
   }
 
-  private static boolean hasName(String name, Region reg) {
-    for (Entry obs : observers) {
-      if (obs.name == null) {
-        continue;
-      }
-      if (name.equals(obs.name)) {
-        if (reg != null && reg == obs.region) {
-          return true;
-        }
-      }
-    }
-    return false;
+  private static boolean hasName(String name) {
+    return observers.containsKey(name);
   }
 
   /**
    * remove the observer from the list, a region observer will be stopped <br>
-   * registered events for that observer are removed as well
+   * events for that observer are removed as well
    *
    * @param name name of observer
    * @return success
    */
-  public static boolean remove(String name) {
-    return remove(null, name);
+  public static void remove(String name) {
+    if (observers.containsKey(name)) {
+      observers.get(name).region.stopObserver();
+      observers.remove(name);
+      events.remove(name);
+    }
   }
 
   /**
    * stop and remove all observers registered for this region from the list <br>
-   * registered events for those observers are removed as well
-   *
-   * @return success
-   */
-  public static boolean remove(Region reg) {
-    return remove(reg, null);
-  }
-
-  /**
-   * stop and remove the observer registered for this region from the list <br>
-   * registered events for that observer are removed as well
-   *
-   * @param reg the observed region
-   * @param name name of observer
-   * @return success
+   * events for those observers are removed as well
+   * @param reg
    */
-  public static synchronized boolean remove(Region reg, String name) {
-    for (Entry obs : observers) {
-      if (name != null) {
-        if (name.equals(obs.name)) {
-          if (reg == null || reg == obs.region) {
-           remove(obs);
-          }
-        }
-      } else if (reg != null && reg == obs.region) {
-        remove(obs);
-      }
-    }
-    return true;
-  }
-
-  private static synchronized void remove(Entry obs) {
-    if (obs.region != null) {
-      obs.region.stopObserver();
-    }
-    for (Event ev:events) {
-      if (ev.observer == obs) {
-        ev.destroy();
-      }
+  public static void remove(Region reg) {
+    for (String name : reg.getObserver().getNames()) {
+      remove(name);
     }
-    obs.destroy();
-  }
-
-  private static Entry get(Region reg, String name) {
-    for (Entry obs : observers) {
-      if (name != null) {
-        if (name.equals(obs.name)) {
-          if (reg == null || reg == obs.region) {
-           return obs;
-          }
-        }
-      } else if (reg != null && reg == obs.region) {
-        return obs;
-      }
-    }
-    return null;
   }
 
   /**
    * stop and remove all observers and their registered events
    *
-   * @return success
    */
-  public static synchronized boolean clear() {
-    log(lvl, "*** requested ***: remove all observers");
-    for (Entry e : observers) {
-      remove(e);
-    }
-    Iterator<Entry> itero = observers.iterator();
-    while (itero.hasNext()) {
-      if (itero.next() == null) {
-        itero.remove();
-      }
-    }
-    Iterator<Event> itere = events.iterator();
-    while (itere.hasNext()) {
-      if (itere.next() == null) {
-        itere.remove();
+  public static void clear() {
+    synchronized (observers) {
+      for (String name : observers.keySet()) {
+        remove(name);
       }
     }
     log(lvl, "as requested: removed all observers");
-    return true;
   }
 
   /**
@@ -268,146 +142,79 @@ public class Observing {
    *
    * @return true if yes
    */
-  public static synchronized boolean hasEvents() {
+  public static boolean hasEvents() {
     return events.size() > 0;
   }
 
   /**
-   * are their any events registered for this region
-   *
-   * @return true if yes
-   */
-  public static synchronized boolean hasEvents(Region reg) {
-    return hasEvent(reg, null);
-  }
-
-  /**
-   * are their any events registered for the observer having this name
+   * are their any events registered for this region?
    *
    * @return true if yes
    */
-  public static synchronized boolean hasEvent(String name) {
-    return hasEvent(null, name);
-  }
-
-  /**
-   * are their any events registered for the region's observer having this name
-   *
-   * @return true if yes
-   */
-  public static synchronized boolean hasEvent(Region reg, String name) {
-    Entry obs = get(reg, name);
-    if (obs == null) {
-      return false;
-    }
-    for (Event ev:events) {
-      if (ev.observer == obs) {
+  public static boolean hasEvents(Region reg) {
+    for (String name : reg.getObserver().getNames()) {
+      if (events.containsKey(name)) {
         return true;
       }
     }
-    return false;
+     return false;
   }
 
   /**
-   * add a new event to the list
+   * are their any events registered for the observer having this name?
    *
-   * @param name name of event
-   * @param pev the event object (ObserveEvent is copied)
-   * @return the time of creation
+   * @return true if yes
    */
-  public static synchronized long addEvent(String name, Object pev) {
-    long t = 0;
-    if (pev instanceof ObserveEvent) {
-      ObserveEvent evt = (ObserveEvent) new Event();
-      ObserveEvent event = (ObserveEvent) pev;
-      evt.type = event.type;
-      evt.setChanges(event.getChanges());
-      evt.setMatch(event.getMatch());
-      evt.setPattern(event.getPattern());
-      evt.setRegion(event.getRegion());
-      pev = evt;
-    }
-    Event ev = (Event) pev;
-    ev.observer = get(null, name);
-    ev.time = new Date().getTime();
-    if (events.add(ev)) {
-      t = ev.time;
-    }
-    Iterator<Event> iter = events.iterator();
-    while (iter.hasNext()) {
-      if (iter.next() == null) {
-        iter.remove();
-      }
-    }
-    return t;
+  public static boolean hasEvent(String name) {
+    return events.containsKey(name);
   }
 
   /**
-   * remove and return the latest event for the named observer <br>
-   * earlier events are removed
+   * add a new event to the list
    *
-   * @param name
-   * @return the event or null if none registered
+   * @param name name of event
    */
-  public static synchronized Event getEvent(String name) {
-    return getEvent(name, true);
-  }
-
-  private static Event getEvent(String name, boolean remove) {
-    Entry obs = get(null, name);
-    Event event = null;
-    if (obs != null) {
-      for (Event ev:events) {
-        if (ev.observer == null) {
-          continue;
-        }
-        if (ev.observer.name.equals(obs.name)) {
-          if (event == null) {
-            event = ev;
-            continue;
-          }
-          if (ev.time > event.time) {
-            event.destroy();
-            event = ev;
-          }
-        }
-      }
-    }
-    if (null != event && remove) {
-      Event ev = new Event(event);
-      event.destroy();
-      event = ev;
-    }
-    return event;
+  public static void addEvent(ObserveEvent evt) {
+    events.put(evt.getName(), evt);
   }
 
   /**
-   * remove and return the latest events for that region <br>
-   * earlier events are removed as well
+   * return the events for that region <br>
+   * events are removed from the list
    *
    * @return the array of events or size 0 array if none
    */
-  public static synchronized Event[] getEvents(Region reg) {
-    List<Event> evts = new ArrayList<Event>();
-    for (Entry obs:observers) {
-      if (reg == obs.region) evts.add(getEvent(obs.name));
+  public static ObserveEvent[] getEvents(Region reg) {
+    List<ObserveEvent> evts = new ArrayList<ObserveEvent>();
+    ObserveEvent evt;
+    for (String name : reg.getObserver().getNames()) {
+      evt = events.get(name);
+      if (evt != null) evts.add(evt);
+      events.remove(name);
     }
-    return evts.toArray(new Event[0]);
+    return evts.toArray(new ObserveEvent[0]);
   }
 
   /**
-   * return the latest events (these are preserved) <br>
-   * earlier events for the same observer are removed
+   * return the all events (they are preserved) <br>
    *
    * @return the array of events or size 0 array if none
    */
-  public static synchronized Event[] getEvents() {
-    List<Event> evts = new ArrayList<Event>();
-    for (Entry obs:observers) {
-      if (obs.name != null) {
-        evts.add(getEvent(obs.name, false));
+  public static ObserveEvent[] getEvents() {
+    List<ObserveEvent> evts = new ArrayList<ObserveEvent>();
+    ObserveEvent evt; 
+    synchronized (events) {      
+      for (String name : events.keySet()) {
+        evt = events.get(name);
+        if (evt == null) {
+          evts.add(evt);
+        }
       }
-    }
-    return evts.toArray(new Event[0]);
+    } 
+    return evts.toArray(new ObserveEvent[0]);
+  }
+  
+  public static void clearEvents() {
+    events.clear();
   }
 }
diff --git a/API/src/main/java/org/sikuli/script/Region.java b/API/src/main/java/org/sikuli/script/Region.java
index 9343f0e..c563d87 100755
--- a/API/src/main/java/org/sikuli/script/Region.java
+++ b/API/src/main/java/org/sikuli/script/Region.java
@@ -81,13 +81,6 @@ public class Region {
    */
   private Observer regionObserver = null;
 
-  public Observer getEvtMgr() {
-    return regionObserver;
-  }
-
-  public void setEvtMgr(Observer em) {
-    regionObserver = em;
-  }
   /**
    * The last found {@link Match} in the Region
    */
@@ -175,6 +168,7 @@ public class Region {
    }
    */
   //</editor-fold>
+  
   //<editor-fold defaultstate="collapsed" desc="Initialization">
   /**
    * Detects on which Screen the Region is present. The region is cropped to the intersection with the given screen or
@@ -355,6 +349,7 @@ public class Region {
   }
 
   //</editor-fold>
+  
   //<editor-fold defaultstate="collapsed" desc="Quasi-Constructors to be used in Java">
   /**
    * internal use only, used for new Screen objects to get the Region behavior
@@ -534,6 +529,7 @@ public class Region {
   }
 
   //</editor-fold>
+
   //<editor-fold defaultstate="collapsed" desc="handle coordinates">
   /**
    * check if current region contains given point
@@ -1217,6 +1213,7 @@ public class Region {
   }
 
   //</editor-fold>
+  
   //<editor-fold defaultstate="collapsed" desc="spatial operators - new regions">
   /**
    * check if current region contains given region
@@ -2474,12 +2471,24 @@ public class Region {
   public boolean isObserving() {
     return observing;
   }
+  
+  /**
+   *
+   * @return true if any events have been before, false otherwise
+   */
+  public boolean hasEvents() {
+    return Observing.hasEvents(this);
+  }
+  
+  public ObserveEvent[] getEvents() {
+    return Observing.getEvents(this);
+  }
 
   /**
    * a subsequently started observer in this region should wait for target
-   * and notify the given observer about this event
-   * for details about the observe event handler: {@link ObserverCallBack}
-   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
+   * and notify the given observer about this event<br />
+   * for details about the observe event handler: {@link ObserverCallBack}<br />
+   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br />
 	 * @param <PSI> Pattern, String or Image
    * @param target
    * @param observer
@@ -2490,6 +2499,19 @@ public class Region {
 	}
 
   /**
+   * a subsequently started observer in this region should wait for target
+   * success and details about the event can be obtained using @{link Observing}<br />
+   * for details about the observe event handler: {@link ObserverCallBack}<br />
+   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br />
+	 * @param <PSI> Pattern, String or Image
+   * @param target
+   * @return the event's name
+   */
+  public <PSI> String onAppear(PSI target) {
+		return onAppearDo(target, null);
+	}
+
+  /**
    *INTERNAL USE ONLY: for use with scripting API bridges
 	 * @param <PSI> Pattern, String or Image
 	 * @param target
@@ -2501,17 +2523,18 @@ public class Region {
 	}
 
   private <PSI> String onAppearDo(PSI target, Object observer) {
-    String name = Observing.add(this, (ObserverCallBack) observer, ObserveEvent.Type.APPEAR);
-    getObserver().addAppearObserver(target, (ObserverCallBack) observer, name);
-    log(lvl, "%s: onAppear: %s with: %s", toStringShort(), name, target);
+    String name = Observing.add(this, 
+            (ObserverCallBack) observer, ObserveEvent.Type.APPEAR, target);
+    log(lvl, "%s: onAppear%s: %s with: %s", toStringShort(), 
+            (observer == null ? "" : " with callback"), name, target);
     return name;
   }
 
   /**
    * a subsequently started observer in this region should wait for the target to vanish
-   * and notify the given observer about this event
-   * for details about the observe event handler: {@link ObserverCallBack}
-   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
+   * and notify the given observer about this event<br />
+   * for details about the observe event handler: {@link ObserverCallBack}<br />
+   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br />
 	 * @param <PSI> Pattern, String or Image
    * @param target
    * @param observer
@@ -2522,6 +2545,19 @@ public class Region {
 	}
 
   /**
+   * a subsequently started observer in this region should wait for the target to vanish
+   * success and details about the event can be obtained using @{link Observing}<br />
+   * for details about the observe event handler: {@link ObserverCallBack}<br />
+   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}<br />
+	 * @param <PSI> Pattern, String or Image
+   * @param target
+   * @return the event's name
+   */
+  public <PSI> String onVanish(PSI target) {
+		return onVanishDo(target, null);
+	}
+
+  /**
    *INTERNAL USE ONLY: for use with scripting API bridges
 	 * @param <PSI> Pattern, String or Image
 	 * @param target
@@ -2533,9 +2569,10 @@ public class Region {
 	}
 
   private <PSI> String onVanishDo(PSI target, Object observer) {
-    String name = Observing.add(this, (ObserverCallBack) observer, ObserveEvent.Type.VANISH);
-    getObserver().addVanishObserver(target, (ObserverCallBack) observer, name);
-    log(lvl, "%s: onVanish: %s with: %s", toStringShort(), name, target);
+    String name = Observing.add(this, 
+            (ObserverCallBack) observer, ObserveEvent.Type.VANISH, target);
+    log(lvl, "%s: onVanish%s: %s with: %s", toStringShort(), 
+            (observer == null ? "" : " with callback"), name, target);
     return name;
   }
 
@@ -2554,6 +2591,18 @@ public class Region {
 
   /**
    * a subsequently started observer in this region should wait for changes in the region
+   * success and details about the event can be obtained using @{link Observing}<br />
+   * for details about the observe event handler: {@link ObserverCallBack}
+   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
+   * @param threshold minimum size of changes (rectangle threshhold x threshold)
+   * @return the event's name
+   */
+  public String onChange(int threshold) {
+    return onChangeDo(threshold, null);
+  }
+
+  /**
+   * a subsequently started observer in this region should wait for changes in the region
    * and notify the given observer about this event <br />
    * minimum size of changes used: Settings.ObserveMinChangedPixels
    * for details about the observe event handler: {@link ObserverCallBack}
@@ -2566,6 +2615,18 @@ public class Region {
   }
 
   /**
+   * a subsequently started observer in this region should wait for changes in the region
+   * success and details about the event can be obtained using @{link Observing}<br />
+   * minimum size of changes used: Settings.ObserveMinChangedPixels
+   * for details about the observe event handler: {@link ObserverCallBack}
+   * for details about APPEAR/VANISH/CHANGE events: {@link ObserveEvent}
+   * @return the event's name
+   */
+  public String onChange() {
+    return onChangeDo(0, null);
+  }
+
+  /**
    *INTERNAL USE ONLY: for use with scripting API bridges
 	 * @param minSize
 	 * @param observer
@@ -2582,7 +2643,8 @@ public class Region {
   public String onChangeDo(int threshold, Object observer) {
     String name = Observing.add(this, (ObserverCallBack) observer, ObserveEvent.Type.CHANGE);
     getObserver().addChangeObserver(threshold, (ObserverCallBack) observer, name);
-    log(lvl, "%s: onChange: %s minSize: %d", toStringShort(), name, threshold);
+    log(lvl, "%s: onChange%s: %s minSize: %d", toStringShort(), 
+            (observer == null ? "" : " with callback"), name, threshold);
     return name;
   }
 
@@ -2626,7 +2688,6 @@ public class Region {
       Debug.error("Region: observe: Nothing to observe (Region might be invalid): " + this.toStringShort());
       return false;
     }
-    Observing.getEvents(this);
     if (observing) {
       Debug.error("Region: observe: already running for this region. Only one allowed!");
       return false;
@@ -2642,7 +2703,7 @@ public class Region {
     }
     regionObserver.initialize();
     observing = true;
-    SikuliX.addRunningObserver(this);
+    Observing.addRunningObserver(this);
     while (observing && stop_t > (new Date()).getTime()) {
       long before_find = (new Date()).getTime();
       ScreenImage simg = getScreen().capture(x, y, w, h);
@@ -2671,7 +2732,6 @@ public class Region {
       log(lvl, "observe: ended successfully: " + this.toStringShort());
       observeSuccess = Observing.hasEvents(this);
     }
-    SikuliX.removeRunningObserver(this);
     return observeSuccess;
   }
 
diff --git a/API/src/main/java/org/sikuli/script/SikuliX.java b/API/src/main/java/org/sikuli/script/SikuliX.java
index 25c0bcf..18b4589 100644
--- a/API/src/main/java/org/sikuli/script/SikuliX.java
+++ b/API/src/main/java/org/sikuli/script/SikuliX.java
@@ -6,8 +6,6 @@
  */
 package org.sikuli.script;
 
-import java.util.ArrayList;
-import java.util.List;
 import org.sikuli.basics.CommandArgs;
 import org.sikuli.basics.Debug;
 import org.sikuli.basics.SikuliScript;
@@ -19,28 +17,6 @@ import org.sikuli.basics.SikuliScript;
 public class SikuliX {
 
   private static final String me = "SikuliX: ";
-  private static List<Region> runningObservers = new ArrayList<Region>();
-
-  public static void addRunningObserver(Region r) {
-    runningObservers.add(r);
-    Debug.log(3, me + "add observer: now running %d observer(s)", runningObservers.size());
-  }
-
-  public static void removeRunningObserver(Region r) {
-    runningObservers.remove(r);
-    Debug.log(3, me + "remove observer: now running %d observer(s)", runningObservers.size());
-  }
-
-  public static void stopRunningObservers() {
-    if (runningObservers.size() > 0) {
-      Debug.log(3, me + "stopping %d running observer(s)", runningObservers.size());
-      for (Region r : runningObservers) {
-        r.stopObserver();
-      }
-      runningObservers.clear();
-    }
-    Observing.clear();
-  }
 
   public static void endNormal(int n) {
     Debug.log(3, me + "endNormal: %d", n);
@@ -73,7 +49,7 @@ public class SikuliX {
   public static void cleanUp(int n) {
     Debug.log(3, me + "cleanUp: %d", n);
     ScreenHighlighter.closeAll();
-    stopRunningObservers();
+    Observing.stopRunningObservers();
     if (CommandArgs.isIDE()) {
       //TODO reset selected options to defaults
     }

-- 
Alioth's /usr/local/bin/git-commit-notice on /srv/git.debian.org/git/pkg-java/sikuli.git



More information about the pkg-java-commits mailing list