001: /*=============================================================================
002: * Copyright Texas Instruments 2002. All Rights Reserved.
003: *
004: * This program is free software; you can redistribute it and/or modify
005: * it under the terms of the GNU General Public License as published by
006: * the Free Software Foundation; either version 2 of the License, or
007: * (at your option) any later version.
008: *
009: * This program is distributed in the hope that it will be useful,
010: * but WITHOUT ANY WARRANTY; without even the implied warranty of
011: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
012: * GNU General Public License for more details.
013: *
014: * You should have received a copy of the GNU General Public License
015: * along with this program; if not, write to the Free Software
016: * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
017: */
018:
019: package ti.chimera.registry;
020:
021: import ti.exceptions.ProgrammingErrorException;
022:
023: import java.util.*;
024:
025: /**
026: * A directory node is simply a regular node whose contents is a directory
027: * table. A directory table is an immutable table, which maps node names to
028: * nodes. A directory table can only be created empty, and only way to
029: * mutate one (ie. add or remove a child) is by using the {@link #add} or
030: * {@link #remove} methods, which return a new directory table. The table
031: * also implements {@link #notIn} to compare two directory tables and
032: * determine the differences between the two. (Note the {@link #notIn}
033: * comparision does not need to be made between successive versions of a
034: * directory table, but can be made between arbitrary directory tables.)
035: * <p>
036: * The directory table is mainly used internally by the registry.
037: * <p>
038: * Note that the current table implementation isn't particularly clever
039: * and basically all operations are O(n) where n is number of children,
040: * except {@link #notIn} which is O(n^2).
041: *
042: * @author ;Rob Clark;a0873619;San Diego;;
043: * @version 0.1
044: */
045: public class DirectoryTable {
046: private String[] names;
047: private Node[] nodes;
048:
049: /**
050: * Class Constructor, create an empty directory table.
051: */
052: public DirectoryTable() {
053: this (new Node[0], new String[0]);
054: }
055:
056: private DirectoryTable(Node[] nodes, String[] names) {
057: if (nodes.length != names.length)
058: throw new ProgrammingErrorException(
059: "this shouldn't happen!");
060:
061: this .nodes = nodes;
062: this .names = names;
063: }
064:
065: /**
066: * Get an iterator of child names of the contents of this directory.
067: *
068: * @return an iterator of child names
069: */
070: public Iterator getChildNames() {
071: /* I could have made the API expose the current implementation
072: * (ie. arrays) but (a) the arrays need to be immutable and (b)
073: * that doesn't make it very easy to change the implementation
074: * to something more efficient at some later date, if needed.
075: */
076: return new oscript.util.CollectionIterator(new Iterator() {
077:
078: private int idx = 0;
079:
080: public boolean hasNext() {
081: return idx < names.length;
082: }
083:
084: public Object next() throws NoSuchElementException {
085: if (!hasNext())
086: throw new NoSuchElementException();
087: return names[idx++];
088: }
089:
090: public void remove() {
091: // XXX I suppose we could support this in the future...
092: // except that we don't have a link back to the node..
093: throw new UnsupportedOperationException("remove");
094: }
095:
096: });
097: }
098:
099: /**
100: * Get the number of children of this directory node.
101: *
102: * @return the number of children
103: * @see #getChildName
104: * @see #getChildNode
105: */
106: public int getChildCount() {
107: return nodes.length;
108: }
109:
110: /**
111: * Get the name of the child at the specified index.
112: *
113: * @param idx the index
114: * @return the child name
115: */
116: String getChildName(int idx) // for now package level protection... I think the Iterator interface should be good enough for everyone else
117: {
118: return names[idx];
119: }
120:
121: /**
122: * Get the node of the child at the specified index.
123: *
124: * @param idx the index
125: * @return the child name
126: */
127: Node getChildNode(int idx) // for now package level protection... I think the Iterator interface should be good enough for everyone else
128: {
129: return nodes[idx];
130: }
131:
132: /**
133: * Get the node in this table with the specified name. Returns
134: * <code>null</code> if none.
135: *
136: * @param name name of node to find
137: * @return the requested node, or <code>null</code> if none
138: */
139: public Node get(String name) {
140: for (int i = 0; i < names.length; i++)
141: if (names[i].equals(name))
142: return nodes[i];
143: return null;
144: }
145:
146: /**
147: * Construct a directory table whose contents is the same as the
148: * current table, with the specified node added. Called by the
149: * registry when linking a new node into the tree, this should
150: * not be called anywhere else. (Nothing to see here, move
151: * along.)
152: *
153: * @param node the node to add
154: * @param name name of node to add
155: * @return the new directory table
156: * @throws RegistryException already contains node with same name
157: */
158: DirectoryTable add(Node node, String name) throws RegistryException {
159: if (get(name) != null)
160: throw new RegistryException("child already exists: " + name);
161:
162: Node[] newNodes = new Node[nodes.length + 1];
163: String[] newNames = new String[names.length + 1];
164:
165: System.arraycopy(nodes, 0, newNodes, 0, nodes.length);
166: System.arraycopy(names, 0, newNames, 0, names.length);
167:
168: newNodes[nodes.length] = node;
169: newNames[names.length] = name.intern();
170:
171: return new DirectoryTable(newNodes, newNames);
172: }
173:
174: /**
175: * Construct a directory table whose contents is the same as the
176: * current table, with the specified node removed. If an attempt
177: * is made to remove a child who is a non-empty directory, this
178: * will throw an exception, because much depends on being able to
179: * detect a node being removed by detecting a change in the node's
180: * parent. Called by the registry when unlinking a node from the
181: * tree, this should not be called by anyone else.
182: *
183: * @param name name of node to remove
184: * @param errIfLastLink should we throw an exception if this
185: * link to the node is the last
186: * @throws RegistryException does not contain node with same name
187: * or if said node is a directory node that still has children
188: * and <code>errIfLastLink</code> is <code>true</code>
189: */
190: DirectoryTable remove(String name, boolean errIfLastLink)
191: throws RegistryException {
192: // find the index of the entry to remove:
193: int idx = -1;
194: for (int i = 0; (i < names.length) && (idx == -1); i++)
195: if (names[i].equals(name))
196: idx = i;
197:
198: if (idx == -1)
199: throw new RegistryException("child doesn't exists: " + name);
200:
201: if (errIfLastLink
202: && (nodes[idx].getValue() instanceof DirectoryTable)
203: && ((DirectoryTable) (nodes[idx].getValue()))
204: .getChildNames().hasNext())
205: throw new RegistryException("child still has children");
206:
207: Node[] newNodes = new Node[nodes.length - 1];
208: String[] newNames = new String[names.length - 1];
209:
210: System.arraycopy(nodes, 0, newNodes, 0, idx);
211: System.arraycopy(names, 0, newNames, 0, idx);
212:
213: System.arraycopy(nodes, idx + 1, newNodes, idx, newNodes.length
214: - idx);
215: System.arraycopy(names, idx + 1, newNames, idx, newNames.length
216: - idx);
217:
218: return new DirectoryTable(newNodes, newNames);
219: }
220:
221: /**
222: * Determine the differences between directory tables, by returning
223: * an array of files that are only contained in this table. For
224: * example:
225: * <pre>
226: * Iterator added = newDirTable.notIn(oldDirTable);
227: * Iterator removed = oldDirTable.notIn(newDirTable);
228: * </pre>
229: * If <code>otherTable</code> is <code>null</code>, this returns
230: * the same thing as {@link #getChildNames}.
231: *
232: * @param otherTable the other table to compare to, or <code>null</code>
233: * @return an iterator of names of children that exist in this table
234: * but not <code>otherTable</code>
235: */
236: public Iterator notIn(DirectoryTable otherTable) {
237: if (otherTable == null)
238: return getChildNames();
239:
240: LinkedList l = new LinkedList();
241:
242: for (int i = 0; i < names.length; i++)
243: l.add(names[i]);
244:
245: for (int i = 0; i < otherTable.names.length; i++)
246: l.remove(otherTable.names[i]);
247:
248: return new oscript.util.CollectionIterator(l.iterator());
249: }
250:
251: /**
252: * for debug...
253: */
254: public String toString() {
255: StringBuffer sb = new StringBuffer();
256:
257: sb.append("[directory table: ");
258:
259: boolean first = true;
260: for (Iterator itr = getChildNames(); itr.hasNext();) {
261: if (first)
262: first = false;
263: else
264: sb.append(", ");
265: sb.append(itr.next());
266: }
267:
268: return sb.append("]").toString();
269: }
270: }
271:
272: /*
273: * Local Variables:
274: * tab-width: 2
275: * indent-tabs-mode: nil
276: * mode: java
277: * c-indentation-style: java
278: * c-basic-offset: 2
279: * eval: (c-set-offset 'substatement-open '0)
280: * eval: (c-set-offset 'case-label '+)
281: * eval: (c-set-offset 'inclass '+)
282: * eval: (c-set-offset 'inline-open '0)
283: * End:
284: */
|