197 lines
6.2 KiB
Java
197 lines
6.2 KiB
Java
/*
|
|
ZZObs.java
|
|
*
|
|
* Copyright (c) 2000, Ted Nelson and Tuomas Lukka
|
|
*
|
|
* You may use and distribute under the terms of either the GNU Lesser
|
|
* General Public License, either version 2 of the license or,
|
|
* at your choice, any later version. Alternatively, you may use and
|
|
* distribute under the terms of the XPL.
|
|
*
|
|
* See the LICENSE.lgpl and LICENSE.xpl files for the specific terms of
|
|
* the licenses.
|
|
*
|
|
* This software is distributed in the hope that it will be useful,
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the README
|
|
* file for more details.
|
|
*
|
|
*/
|
|
/*
|
|
* Written by Tuomas Lukka, queuing and enable/disable by Rauli Ruohonen
|
|
*/
|
|
|
|
package org.gzigzag;
|
|
import java.util.*;
|
|
|
|
/** A class used to trigger ZZObses.
|
|
*/
|
|
|
|
public class ZZObsTrigger {
|
|
String rcsid = "$Id: ZZObsTrigger.java,v 1.6.2.1 2001/04/08 16:13:59 ajk Exp $";
|
|
public static boolean dbg = false;
|
|
private static void p(String s) { if(dbg) ZZLogger.log(s); }
|
|
private static void pa(String s) { ZZLogger.log(s); }
|
|
|
|
// XXX Store weak references, not hard ones!
|
|
private static Vector allTrigs = new Vector();
|
|
private static Hashtable obsQueue = new Hashtable();
|
|
private static Hashtable obsDisabled = new Hashtable();
|
|
|
|
ZZObsTrigger() {
|
|
allTrigs.addElement(this);
|
|
}
|
|
|
|
/** An object which keeps track of the observations of a single
|
|
* ZZObs.
|
|
*/
|
|
class Gob {
|
|
/** Linked list of Gobs, plus list of all objects that this
|
|
* Gob observes.
|
|
* This is a tricky part and this is also what makes us blazingly
|
|
* fast. And difficult to explain.
|
|
* <p>
|
|
* First of all, there is a key for every object that this
|
|
* gob's <code>obs</code> has been <code>addObs</code>'ed for.
|
|
* The value depends on whether there are other gobs observing
|
|
* the same thing. If there are, then the hashtables form a linked
|
|
* list. If not, or if this is the last element of the linked
|
|
* list, the value in the Hashtable is <strong>this gob</strong>.
|
|
* That may seem odd until you realize that 1) hashtables can't
|
|
* store nulls, and 2) <code>this</code> is a pointer that is
|
|
* very likely to be in a register so fast to compare with
|
|
* the reference.
|
|
*/
|
|
Hashtable next = new Hashtable();
|
|
/** The backward links. Only necessary links are stored. */
|
|
Hashtable prev = new Hashtable();
|
|
ZZObs obs;
|
|
}
|
|
|
|
/** Key: ZZObs; Value: Gob. */
|
|
Hashtable gobs = new Hashtable();
|
|
|
|
/** Key: Object (trigger); Value: Gob. */
|
|
Hashtable trigs = new Hashtable();
|
|
|
|
|
|
/** Called when something is changed.
|
|
* This call removes instances of the ZZObses triggered from
|
|
* <em>all</em> existing ZZObsTrigger objects.
|
|
*/
|
|
public synchronized void chg(Object o) {
|
|
Gob g,pg=null;
|
|
Hashtable next=trigs;
|
|
while((g = (Gob)next.get(o)) !=null && g!=pg) {
|
|
pg=g;
|
|
if(!chg(g.obs)) next=g.next;
|
|
}
|
|
// XXX This shouldn't be necessary! (views should use triggers)
|
|
ZZUpdateManager.chg();
|
|
}
|
|
public static boolean chg(ZZObs obs) {
|
|
// XXX This shouldn't be necessary! (views should use triggers)
|
|
ZZUpdateManager.chg();
|
|
synchronized(obsQueue) {
|
|
if(obsDisabled.containsKey(obs)) return false;
|
|
obsQueue.put(obs,obs);
|
|
for(int i=0; i<allTrigs.size(); i++)
|
|
((ZZObsTrigger)allTrigs.elementAt(i)).rmObs(obs);
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/** Called to add an observer.
|
|
*/
|
|
public synchronized void addObs(Object o, ZZObs obs) {
|
|
Object go = gobs.get(obs);
|
|
Gob g;
|
|
if(go==null) {
|
|
gobs.put(obs, g = new Gob());
|
|
g.obs = obs;
|
|
} else {
|
|
g = (Gob)go;
|
|
}
|
|
if(g.next.get(o) != null) return; // Already observing.
|
|
Gob t = (Gob)trigs.get(o);
|
|
trigs.put(o, g);
|
|
if(t==null) g.next.put(o,g); // First observer for this object.
|
|
else {
|
|
g.next.put(o,t);
|
|
t.prev.put(o,g);
|
|
}
|
|
}
|
|
|
|
/** Called to remove observers.
|
|
*/
|
|
public synchronized void rmObs(ZZObs obs) {
|
|
Gob g = (Gob)gobs.get(obs);
|
|
if(g == null) return; // Wasn't observing anything
|
|
gobs.remove(obs);
|
|
for(Enumeration e = g.next.keys(); e.hasMoreElements(); ) {
|
|
Object o = e.nextElement();
|
|
Gob ng = (Gob)g.next.get(o);
|
|
Gob pg = (Gob)g.prev.get(o);
|
|
if(pg==null) {
|
|
if(ng==g) trigs.remove(o);
|
|
else {
|
|
trigs.put(o,ng);
|
|
ng.prev.remove(o);
|
|
}
|
|
} else {
|
|
if(ng==g) pg.next.put(o,pg);
|
|
else {
|
|
pg.next.put(o,ng);
|
|
ng.prev.put(o,pg);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/** Does the actual triggering.
|
|
* XXX We must continue triggering until no new triggers are created,
|
|
* which is bad if there are lots of (or infinite amount, in case of a
|
|
* bug) triggers. This is because we must run triggers in the AWT event
|
|
* processing thread (+), and unless we use JDK 1.2 "EventQueue.invoke*()"
|
|
* methods or resort to some horrid dummy-event kludge, we can't expect
|
|
* any triggers we leave behind to be triggered until the next AWT event,
|
|
* which is unacceptable. ("- See, it doesn't display the new info if I
|
|
* don't move the mouse around! - Are you *sure* it isn't a M$ product?")
|
|
* This isn't a real problem at the moment, though.
|
|
*
|
|
* (+) The event thread must always be runnable, or we risk a deadlock,
|
|
* and most of ZZ structure -using code isn't thread-safe => only the
|
|
* event thread may call general ZZ structure -using code.
|
|
*/
|
|
public static void runObsQueue() {
|
|
p("runObsQueue enter");
|
|
synchronized(obsQueue) {
|
|
while(obsQueue.size()>0) {
|
|
p("STARTTRIGSEQ "+(obsQueue.size()));
|
|
/* Trigger the queued observers in some order */
|
|
Hashtable q=obsQueue;
|
|
obsQueue=new Hashtable();
|
|
for(Enumeration e=q.keys();e.hasMoreElements();)
|
|
try {
|
|
((ZZObs)e.nextElement()).chg();
|
|
} catch(Exception ex) {
|
|
ZZLogger.exc(ex);
|
|
System.out.println("EXCEPTION WHILE TRIGGERING!");
|
|
}
|
|
p("ENDTRIGSEQ "+(obsQueue.size()));
|
|
}
|
|
}
|
|
p("runObsQueue leave");
|
|
}
|
|
|
|
/** Enable/disable an observer.
|
|
* If an observer is disabled, chg(o) won't trigger it. chg(o) calls
|
|
* made before calling this method will be unaffected - that is, if
|
|
* obs is in obsQueue, it will be triggered whether it is enabled or not.
|
|
*/
|
|
public static void setEnabled(ZZObs obs,boolean t) {
|
|
if(t) obsDisabled.remove(obs);
|
|
else obsDisabled.put(obs,obs);
|
|
}
|
|
}
|