001: package bsh;
002:
003: import java.util.*;
004:
005: /**
006: A namespace which maintains an external map of values held in variables in
007: its scope. This mechanism provides a standard collections based interface
008: to the namespace as well as a convenient way to export and view values of
009: the namespace without the ordinary BeanShell wrappers.
010: </p>
011:
012: Variables are maintained internally in the normal fashion to support
013: meta-information (such as variable type and visibility modifiers), but
014: exported and imported in a synchronized way. Variables are exported each
015: time they are written by BeanShell. Imported variables from the map appear
016: in the BeanShell namespace as untyped variables with no modifiers and
017: shadow any previously defined variables in the scope.
018: <p/>
019:
020: Note: this class is inherentely dependent on Java 1.2 (for Map), however
021: it is not used directly by the core as other than type NameSpace, so no
022: dependency is introduced.
023: */
024: /*
025: Implementation notes:
026:
027: It would seem that we should have been accomplished this by overriding the
028: getImportedVar() method of NameSpace, which behaves in a similar way
029: for fields of classes and objects. However we need more control here to
030: be able to bump up the precedence and remove items that have been removed
031: via the map. So we override getVariableImp(). We should reevaluate this
032: at some point. All of NameSpace is a mess.
033:
034: The primary abstraction here is that we override createVariable() to
035: create LHS Variables bound to the map for this namespace.
036:
037: Methods:
038:
039: bsh methods are not currently exported to the
040: external namespace. All that would be required to add this is to override
041: setMethod() and provide a friendlier view than vector (currently used) for
042: overloaded forms (perhaps a map by method SignatureKey).
043:
044: */
045: public class ExternalNameSpace extends NameSpace {
046: private Map externalMap;
047:
048: public ExternalNameSpace() {
049: this (null, "External Map Namespace", null);
050: }
051:
052: /**
053: */
054: public ExternalNameSpace(NameSpace parent, String name,
055: Map externalMap) {
056: super (parent, name);
057:
058: if (externalMap == null)
059: externalMap = new HashMap();
060:
061: this .externalMap = externalMap;
062:
063: }
064:
065: /**
066: Get the map view of this namespace.
067: */
068: public Map getMap() {
069: return externalMap;
070: }
071:
072: /**
073: Set the external Map which to which this namespace synchronizes.
074: The previous external map is detached from this namespace. Previous
075: map values are retained in the external map, but are removed from the
076: BeanShell namespace.
077: */
078: public void setMap(Map map) {
079: // Detach any existing namespace to preserve it, then clear this
080: // namespace and set the new one
081: this .externalMap = null;
082: clear();
083: this .externalMap = map;
084: }
085:
086: /**
087: */
088: public void unsetVariable(String name) {
089: super .unsetVariable(name);
090: externalMap.remove(name);
091: }
092:
093: /**
094: */
095: public String[] getVariableNames() {
096: // union of the names in the enclosing namespace and external map
097: Set nameSet = new HashSet();
098: String[] nsNames = super .getVariableNames();
099: nameSet.addAll(Arrays.asList(nsNames));
100: nameSet.addAll(externalMap.keySet());
101: return (String[]) nameSet.toArray(new String[0]);
102: }
103:
104: /**
105: */
106: /*
107: Notes: This implementation of getVariableImpl handles the following
108: cases:
109: 1) var in map not in local scope - var was added through map
110: 2) var in map and in local scope - var was added through namespace
111: 3) var not in map but in local scope - var was removed via map
112: 4) var not in map and not in local scope - non-existent var
113:
114: Note: It would seem that we could simply override getImportedVar()
115: in NameSpace, rather than this higher level method. However we need
116: more control here to change the import precedence and remove variables
117: if they are removed via the extenal map.
118: */
119: protected Variable getVariableImpl(String name, boolean recurse)
120: throws UtilEvalError {
121: // check the external map for the variable name
122: Object value = externalMap.get(name);
123:
124: Variable var;
125: if (value == null) {
126: // The var is not in external map and it should therefore not be
127: // found in local scope (it may have been removed via the map).
128: // Clear it prophalactically.
129: super .unsetVariable(name);
130:
131: // Search parent for var if applicable.
132: var = super .getVariableImpl(name, recurse);
133: } else {
134: // Var in external map may be found in local scope with type and
135: // modifier info.
136: Variable localVar = super .getVariableImpl(name, false);
137:
138: // If not in local scope then it was added via the external map,
139: // we'll wrap it and pass it along. Else we'll use the one we
140: // found.
141: if (localVar == null)
142: var = createVariable(name, null/*type*/, value, null/*mods*/);
143: else
144: var = localVar;
145: }
146:
147: return var;
148: }
149:
150: public Variable createVariable(String name, Class type,
151: Object value, Modifiers mods) {
152: LHS lhs = new LHS(externalMap, name);
153: // Is this race condition worth worrying about?
154: // value will appear in map before it's really in the interpreter
155: try {
156: lhs.assign(value, false/*strict*/);
157: } catch (UtilEvalError e) {
158: throw new InterpreterError(e.toString());
159: }
160: return new Variable(name, type, lhs);
161: }
162:
163: /**
164: Clear all variables, methods, and imports from this namespace and clear
165: all values from the external map (via Map clear()).
166: */
167: public void clear() {
168: super.clear();
169: externalMap.clear();
170: }
171:
172: }
|