282 lines
8.5 KiB
Java
282 lines
8.5 KiB
Java
/*
|
|
ZZCacheDimension.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
|
|
*/
|
|
package org.gzigzag;
|
|
import java.util.*;
|
|
|
|
/** Dimension-centric implementation of a locally
|
|
* cached dimension.
|
|
* This class may be used as a cache to
|
|
* a dimension stored on disk, and includes facilities for tracking
|
|
* the changes.
|
|
* XXX Look into replacing hashtable with a more memory-saving constructions?
|
|
* <p>
|
|
* This, uglily, shares some code with ZZLocalDimension but making
|
|
* this an inheritance relationship would have been difficult, and
|
|
* the point is to keep ZZLocalDimension a truly simple basic
|
|
* example implementation of a dimension.
|
|
* <p>
|
|
* The mechanism for the undolist is quite interesting:
|
|
* we save each connect / disconnect operation with the two cells,
|
|
* so we know the inverse of each operation.
|
|
* We can then, at commit time, build a hash table by taking the first
|
|
* operation and seeing if it cancels an earlier operation (undolist does
|
|
* commits in reverse order) and if it does, we do nothing.
|
|
* This allows the user to move the cursor without wasting storage on all
|
|
* positions.
|
|
* Interestingly, although we store all connections bidirectionally as
|
|
* connect/disconnect pairs in the undolist, we cannot save them like that
|
|
* after compression: they must be saved as single-sided disconnects and
|
|
* pair connects.
|
|
*/
|
|
public class ZZCacheDimension extends ZZDimension implements ZZDimStorer {
|
|
public static final String rcsid = "$Id: ZZCacheDimension.java,v 1.11 2001/02/23 08:33:37 ajk Exp $";
|
|
public static final boolean dbg = true;
|
|
final static void p(String s) { if(dbg) System.out.println(s); }
|
|
final static void pa(String s) { System.out.println(s); }
|
|
|
|
UndoList undo;
|
|
public void setUndoList(UndoList l) { undo = l; }
|
|
|
|
protected boolean readonly;
|
|
|
|
public ZZCacheDimension() { this(false); }
|
|
public ZZCacheDimension(boolean readonly) {
|
|
this.readonly = readonly;
|
|
}
|
|
|
|
/** Connections to the positive direction. */
|
|
Hashtable cp = new Hashtable();
|
|
/** Connections to the negative direction. */
|
|
Hashtable cm = new Hashtable();
|
|
|
|
/** The earlier connection on each cell's poswards or negwards
|
|
* direction at commit time.
|
|
* This list is created at commit time by reversing all operations
|
|
* so we can then compare these values and the current values to
|
|
* see which have changed.
|
|
* Hmm.. do we really even need comm?
|
|
*/
|
|
Hashtable comp;
|
|
Hashtable comm;
|
|
|
|
/** A null marker for the hashtables. */
|
|
static final Object nolla = new Object();
|
|
|
|
synchronized public void startCommit() {
|
|
if (readonly) throw new ZZError("readonly dimension");
|
|
comp = new Hashtable();
|
|
comm = new Hashtable();
|
|
}
|
|
|
|
synchronized public void endCommit(ZZDimStorer sto) {
|
|
if (readonly) throw new ZZError("readonly dimension");
|
|
for(Enumeration e = comp.keys();
|
|
e.hasMoreElements();) {
|
|
String id = (String)e.nextElement();
|
|
Object oidto = comp.get(id);
|
|
String idto = (oidto == nolla ? null : (String)oidto);
|
|
String idwasto = (String)cp.get(id);
|
|
// If it's the same, then nevermind.
|
|
if(idto == idwasto || (
|
|
idto != null && idto.equals(idwasto)))
|
|
continue;
|
|
if(idwasto != null)
|
|
sto.storeConnect(id, idwasto);
|
|
else
|
|
sto.storeDisconnect(id, 1);
|
|
|
|
}
|
|
for(Enumeration e = comm.keys();
|
|
e.hasMoreElements();) {
|
|
String id = (String)e.nextElement();
|
|
Object oidto = comm.get(id);
|
|
String idto = (oidto == nolla ? null : (String)oidto);
|
|
String idwasto = (String)cm.get(id);
|
|
// If it's the same, then nevermind.
|
|
if(idto == idwasto || (
|
|
idto != null && idto.equals(idwasto)))
|
|
continue;
|
|
if(idwasto == null)
|
|
sto.storeDisconnect(id, -1);
|
|
// Not here: we only call connect once.
|
|
// else
|
|
// sto.connect(id, -1, idto);
|
|
|
|
}
|
|
comp = null;
|
|
comm = null;
|
|
}
|
|
|
|
ZZCellHandle ch(Object o) {
|
|
if(o instanceof ZZCellHandle) return (ZZCellHandle)o;
|
|
return (ZZCellHandle)space.getCellByID((String)o);
|
|
}
|
|
String cid(Object o) {
|
|
if(o instanceof ZZCellHandle) return ((ZZCellHandle)o).id;
|
|
return (String)o;
|
|
}
|
|
|
|
/** The abstracted operation of connecting two cells.
|
|
* Parameters: cell1 cell2.
|
|
* The cells are explicitly assumed to be not connected before
|
|
* this.
|
|
*/
|
|
protected class Connect implements UndoList.Op {
|
|
public void undo(Object[] list, int nth) {
|
|
String c1 = (String)list[nth+1];
|
|
String c2 = (String)list[nth+2];
|
|
if(!s((ZZCellHandle)space.getCellByID(c1), 1, null).id.equals(c2))
|
|
throw new ZZError(
|
|
"Error when undoing change: disconnect assertion broken");
|
|
cp.remove(c1);
|
|
cm.remove(c2);
|
|
triggers.chg(c1);
|
|
triggers.chg(c2);
|
|
}
|
|
public void redo(Object[] list, int nth) {
|
|
String c1 = (String)list[nth+1];
|
|
String c2 = (String)list[nth+2];
|
|
if(cp.get(c1) != null ||
|
|
cm.get(c2) != null)
|
|
throw new ZZError(
|
|
"Error when redoing change: unconnected assertion broken");
|
|
cp.put(c1, c2);
|
|
cm.put(c2, c1);
|
|
triggers.chg(c1);
|
|
triggers.chg(c2);
|
|
}
|
|
public void commit(Object[] list, int nth) {
|
|
String c1 = (String)list[nth+1];
|
|
String c2 = (String)list[nth+2];
|
|
comp.put(c1, nolla);
|
|
comm.put(c2, nolla);
|
|
}
|
|
}
|
|
|
|
|
|
/** The abstracted operation of disconnecting two cells.
|
|
* Parameters: cell1 cell2.
|
|
*/
|
|
protected class Disconnect implements UndoList.Op {
|
|
public void undo(Object[] list, int nth) {
|
|
String c1 = (String)list[nth+1];
|
|
String c2 = (String)list[nth+2];
|
|
// Note we can't use disconnect() since that would make
|
|
// new undolist entries.
|
|
if(cp.get(c1) != null ||
|
|
cm.get(c2) != null)
|
|
throw new ZZError(
|
|
"Error when undoing change: unconnected assertion broken");
|
|
cp.put(c1, c2);
|
|
cm.put(c2, c1);
|
|
triggers.chg(c1);
|
|
triggers.chg(c2);
|
|
}
|
|
public void redo(Object[] list, int nth) {
|
|
String c1 = (String)list[nth+1];
|
|
String c2 = (String)list[nth+2];
|
|
if(!s((ZZCellHandle)space.getCellByID(c1), 1, null).id.equals(c2))
|
|
throw new ZZError(
|
|
"Error when redoing change: disconnect assertion broken");
|
|
cp.remove(c1);
|
|
cm.remove(c2);
|
|
triggers.chg(c1);
|
|
triggers.chg(c2);
|
|
}
|
|
public void commit(Object[] list, int nth) {
|
|
String c1 = (String)list[nth+1];
|
|
String c2 = (String)list[nth+2];
|
|
comp.put(c1, c2);
|
|
comm.put(c2, c1);
|
|
}
|
|
}
|
|
|
|
Connect myconnect = new Connect();
|
|
Disconnect mydisconnect = new Disconnect();
|
|
|
|
public ZZCellHandle s(ZZCellHandle c, int steps, ZZObs o) {
|
|
String s = c.id;
|
|
if(o != null) triggers.addObs(s, o);
|
|
if(steps>0)
|
|
while(steps-- > 0 && s != null) {
|
|
s = (String)cp.get(s);
|
|
if(o != null && s!=null) triggers.addObs(s, o);
|
|
}
|
|
else
|
|
while(steps++ < 0 && s != null) {
|
|
s = (String)cm.get(s);
|
|
if(o != null && s!=null) triggers.addObs(s, o);
|
|
}
|
|
if(s==null) return null;
|
|
return (ZZCellHandle)space.getCellByID(s);
|
|
}
|
|
|
|
public void connect(ZZCellHandle c0, ZZCellHandle d0) {
|
|
if (readonly) throw new ZZError("readonly dimension");
|
|
String c = c0.id;
|
|
String d = d0.id;
|
|
disconnect(c0, 1);
|
|
disconnect(d0, -1);
|
|
cp.put(c, d);
|
|
cm.put(d, c);
|
|
undo.add(myconnect, c, d);
|
|
triggers.chg(c);
|
|
triggers.chg(d);
|
|
}
|
|
|
|
public void disconnect(ZZCellHandle c0, int dir) {
|
|
if (readonly) throw new ZZError("readonly dimension");
|
|
String c = c0.id;
|
|
String o;
|
|
if(dir>0) {
|
|
o = (String)cp.get(c);
|
|
if(o!=null) {
|
|
undo.add(mydisconnect, c, o);
|
|
cp.remove(c);
|
|
cm.remove(o);
|
|
}
|
|
} else {
|
|
o = (String)cm.get(c);
|
|
if(o!=null) {
|
|
undo.add(mydisconnect, o, c);
|
|
cm.remove(c);
|
|
cp.remove(o);
|
|
}
|
|
}
|
|
triggers.chg(c);
|
|
if(o!=null) triggers.chg(o);
|
|
}
|
|
|
|
// ZZDimStorer implementation
|
|
|
|
public void storeConnect(String a, String b) {
|
|
cp.put(a, b);
|
|
cm.put(b, a);
|
|
}
|
|
public void storeDisconnect(String a, int dir) {
|
|
Hashtable h = (dir > 0 ? cp : cm);
|
|
h.remove(a);
|
|
}
|
|
|
|
}
|