samer@0: /* samer@0: * Environment.java samer@0: * samer@0: * Copyright (c) 2001, Samer Abdallah, King's College London. samer@0: * All rights reserved. samer@0: * samer@0: * This software is provided AS IS and WITHOUT ANY WARRANTY; samer@0: * without even the implied warranty of MERCHANTABILITY or samer@0: * FITNESS FOR A PARTICULAR PURPOSE. samer@0: */ samer@0: samer@0: package samer.core; samer@0: samer@0: /** samer@0: An Environment is a way of managing named values. samer@0: The names form a heirarchical system using dot (.) as samer@0: a seperator. (See Node class) samer@0: samer@0: Each Environment has a parent Environment, except for the samer@0: singleton top level Environment. samer@0: samer@0: Each Environment has a Node which serves as a 'current samer@0: directory'. Environments form a chain: if one samer@0: Environment cannot satisfy a request for a named samer@0: binding, it should ask its parent. samer@0: samer@0: @see samer.core.Node samer@0: @see samer.core.X samer@0: */ samer@0: samer@0: public class Environment samer@0: { samer@0: protected Environment parent; samer@0: protected Node base; samer@0: samer@0: /** Construct a new Environment with the given parent samer@0: and base node */ samer@0: public Environment(Environment parent, Node base) { this.parent=parent; this.base=base; } samer@0: samer@0: /** Construct a new Environment with the given parent samer@0: and a base node relative to the parent Environment. samer@0: That is, node = new Node(name, parent.node()); samer@0: */ samer@0: public Environment(Environment parent, String name) { samer@0: this(parent,new Node(name,parent.base)); samer@0: } samer@0: samer@0: /** Return this environment's parent, or null of top level */ samer@0: public final Environment parent() { return parent; } samer@0: samer@0: /** Return this environment's associated node */ samer@0: public final Node node() { return base; } samer@0: samer@0: /* samer@0: * these methods form the main implementation of the Environment and samer@0: * are meant to be overridden by subclasses. samer@0: */ samer@0: samer@0: /** Add and return a new named binding */ samer@0: public Binding add(String nm, Object o) { return parent.add(abs(nm),o); } samer@0: samer@0: /** Add anonymous datum */ samer@0: public void add(Object obj) { parent.add(obj); } samer@0: samer@0: public void store(String nm, Autocoder o) { parent.store(abs(nm),o); } samer@0: public void store(String nm, Object o, Codec c) { parent.store(abs(nm),o,c); } samer@0: samer@0: /** Return next anonymous datum */ samer@0: public Datum datum() { return parent.datum(); } samer@0: samer@0: /** Return Datum that best matches given name */ samer@0: public Datum datum(String name) { return parent.datum(abs(name)); } samer@0: samer@0: /** Return Binding that best matches given name */ samer@0: public Binding binding(String name) { return parent.binding(abs(name)); } samer@0: samer@0: /** Return an Iterator that will step through all data in this samer@0: Environment and it's ancestors */ samer@0: public Iterator data() { return parent.data(); } samer@0: samer@0: // ... Interfaces .................................................... samer@0: samer@0: /** samer@0: Basic interface for getting at a particular stored object or samer@0: value. samer@0: There is no concept of returning the value as-is: it must samer@0: be translated in to an object of some desired class. samer@0: Value can be extracted in one of two ways: samer@0: a Codec, which is an object which knows how to convert samer@0: between stored data and some desired class of object. samer@0: The other way is via an Autocoder, which is basically samer@0: an object with it's own Codec built in. samer@0: */ samer@0: samer@0: public interface Datum { samer@0: samer@0: /** the stored name of this Datum */ samer@0: String name(); samer@0: samer@0: /** goodness of name matching procedure used to find this Datum */ samer@0: int score(); samer@0: samer@0: // Object get() throws Exception; // return intrinsic class ?? samer@0: // Object get(Codec c) throws Exception; // no default supplied samer@0: samer@0: /** return object as decoded by Codec c. If this Datum is a samer@0: null Datum, ie contains no data, return def */ samer@0: Object get(Codec c, Object def); // ?? copy semantics? exception? samer@0: samer@0: /** decode value in to Autocoder obj */ samer@0: void get(Autocoder obj) throws Exception; samer@0: samer@0: } samer@0: samer@0: /** samer@0: A Binding is a Datum in which the value is an accessible Object, samer@0: ie one that doesn't need decoding. Unlike a Datum, this value samer@0: can be set or removed from the Environment. samer@0: */ samer@0: public interface Binding extends Datum { samer@0: Object get() throws Exception; samer@0: void set(Object obj); samer@0: void remove(); samer@0: } samer@0: samer@0: /** samer@0: A null Binding that has no data. samer@0: All attempts to read value throw an Exception, except get() samer@0: with a default supplied. samer@0: */ samer@0: public final static Binding Null = new Binding() { samer@0: public String name() { return "null"; } samer@0: public int score() { return 1000000000; } // Mr. Billion samer@0: samer@0: public Object get() throws Exception { throw new Exception(); } samer@0: public Object get(Codec c, Object def) { return def==null ? def : c.decode(def); } // ?? samer@0: // public void get(Autocoder obj) throws Exception { throw new Exception(); } samer@0: public void get(Autocoder obj) {} samer@0: samer@0: public void set(Object obj) { throw new RuntimeException(); } samer@0: public void remove() { throw new RuntimeException(); } samer@0: }; samer@0: samer@0: /** This is an object which knows how to code and decode itself. */ samer@0: public interface Autocoder samer@0: { samer@0: /** return this object as a string */ samer@0: String string(); // as string samer@0: samer@0: /** return this object as any object of a convenient class. samer@0: Strictly speaking, should return an immutable samer@0: representation of the object. samer@0: */ samer@0: Object object(); samer@0: samer@0: /** set this object by decoding supplied object, which may be samer@0: a string. This method can be as smart as you like, behaving samer@0: differently for different classes of supplied object. samer@0: */ samer@0: void decode(Object o); samer@0: } samer@0: samer@0: /** This is an object which knows how to code and decode on behalf samer@0: of some other class which represents the actual data. */ samer@0: public interface Codec { samer@0: /** return Class of object that this Codec deals with */ samer@0: Class targetClass(); samer@0: samer@0: // methods for encoding object for storage samer@0: String string(Object o); // convert object to string samer@0: Object object(Object o); // convert object to another object samer@0: Object decode(Object o); // convert other object to target class samer@0: } samer@0: samer@0: /** For iterating through data in environment, samer@0: returning a Datum for each item samer@0: */ samer@0: public interface Iterator { samer@0: boolean hasNext(); samer@0: Datum next(); samer@0: } samer@0: samer@0: /** Steps through two iterators consecutively */ samer@0: protected static class CompoundIterator implements Iterator { samer@0: Iterator i1, i2; samer@0: public CompoundIterator(Iterator a, Iterator b) { i1=a; i2=b; } samer@0: public boolean hasNext() { samer@0: if (i1.hasNext()) return true; samer@0: if (i2==null) return false; samer@0: i1=i2; i2=null; return i1.hasNext(); samer@0: } samer@0: public Datum next() { samer@0: if (i1.hasNext()) return i1.next(); samer@0: i1=i2; i2=null; return i1.next(); samer@0: } samer@0: } samer@0: samer@0: // ................................................................... samer@0: samer@0: /** Return absolute node path for given name. If name begins samer@0: with '.', then it is already an absolute name. Otherwise, the samer@0: base node for this Environment is prepended. samer@0: */ samer@0: public final String abs(String name) { samer@0: return Node.isAbsolute(name) ? name : base.fullNameFor(name); samer@0: } samer@0: samer@0: /** Return node name relative to this Environment's node. samer@0: Throws an Exception if the supplied name is absolute and samer@0: NOT a child of this Environment's node. samer@0: */ samer@0: public final String rel(String name) { samer@0: if (!Node.isAbsolute(name)) return name; samer@0: String fullbase=base.fullName(); samer@0: if (!name.startsWith(fullbase)) throw new Error("nonlocal node name:" + name); samer@0: return name.substring(fullbase.length()+1); // miss out dot! samer@0: } samer@0: samer@0: /** Returns true if the given name is a subnode of this environment's node, samer@0: ie, if it can belong in this environment samer@0: */ samer@0: protected boolean belongs(String name) { return base.isSubnode(name); } samer@0: samer@0: /** Return a top level environment */ samer@0: public static Environment top() { samer@0: return new Environment(null,Node.root()) { samer@0: public Binding add(String n, Object o) { throw error(); } samer@0: public void add(Object o) { throw error(); } samer@0: public void store(String nm, Object o, Codec c) { throw error(); } samer@0: public void store(String nm, Autocoder o) { throw error(); } samer@0: samer@0: public Datum datum() { return Null; } samer@0: public Datum datum(String name) { return Null; } samer@0: public Binding binding(String name) { return Null; } samer@0: samer@0: public Iterator data() { samer@0: return new Iterator() { samer@0: public boolean hasNext() { return false; } samer@0: public Datum next() { return Null; } samer@0: }; samer@0: } samer@0: samer@0: RuntimeException error() { return new RuntimeException("Environment.top"); } samer@0: }; samer@0: } samer@0: } samer@0: