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: