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.chimera.Main;
022: import ti.exceptions.ProgrammingErrorException;
023: import ti.pub.WorkerThread;
024:
025: import java.util.LinkedList;
026: import java.util.Iterator;
027:
028: import javax.swing.JOptionPane;
029:
030: /**
031: * Base class for a node in the tree. A node can have an optional contract,
032: * which constraints what values can be assigned to this node. If no
033: * contract is specified, the null-contract (ie. accept any value) will be
034: * used.
035: * <p>
036: * Clients can subscribe to the value of the node, in which case every time
037: * the node's value is changed, the new value will be published to them.
038: * (Also, the current value is immediately published, upon subscription.)
039: * All changes to a node are published in the order they occur.
040: * <p>
041: * NOTE: currently it is possible that a value be published to a subscriber
042: * <i>after</i> the subscriber has been unsubscribed, but only if the change
043: * that is being published occurred <i>before</i> the subscriber unsubscribed.
044: *
045: * @author ;Rob Clark;a0873619;San Diego;;
046: * @version 0.1
047: */
048: public class Node {
049: /**
050: * The node's contract, determines what sorts of values can be assigned to
051: * the node.
052: */
053: private NodeContract contract;
054:
055: /**
056: * The node's current value
057: */
058: private Object value;
059:
060: /**
061: * A description of the node.
062: */
063: private String comment;
064:
065: /**
066: * The list of paths this node is currently linked in to, starting
067: * with the earliest path linked to.
068: */
069: private LinkedList pathList = new LinkedList();
070:
071: /**
072: * The list of node-subscribers.
073: */
074: private LinkedList subscriberList = new LinkedList();
075:
076: /**
077: * The cached array of subscribers... this is nuked whenever the
078: * subscriber list changes, but we cache it to avoid repeated
079: * toArray()s whenever the value is changed
080: */
081: private NodeSubscriber[] subscribers = null;
082:
083: /*=======================================================================*/
084: /**
085: * Class Constructor.
086: *
087: * @param value the node's initial value
088: * @param contract the node's contract, or <code>null</code>
089: * @param comment a string containing a description of the purpose
090: * of this node, the node's usage, etc. Can contain HTML markup.
091: */
092: public Node(Object value, NodeContract contract, String comment) {
093: if (contract == null)
094: contract = NodeContract.NULL_CONTRACT;
095:
096: this .contract = contract;
097: this .comment = comment;
098:
099: setValue(value);
100: }
101:
102: /*=======================================================================*/
103: /**
104: * Get the node's contract.
105: *
106: * @return the <code>NodeContract</code>
107: */
108: public NodeContract getNodeContract() {
109: return contract;
110: }
111:
112: /*=======================================================================*/
113: /**
114: * Get the node's comment.
115: *
116: * @return a string
117: */
118: public String getComment() {
119: return comment;
120: }
121:
122: /*=======================================================================*/
123: /**
124: * Set the value of this node, and publish the new value to all the
125: * subscribers.
126: *
127: * @param value the node's new value
128: */
129: public void setValue(Object value) {
130: if (!contract.accepts(value))
131: throw new ProgrammingErrorException("value (" + value
132: + ") does not meet contract (" + contract + ")");
133:
134: synchronized (RegistryCore.lock) {
135: this .value = value;
136:
137: if (subscribers == null)
138: subscribers = (NodeSubscriber[]) (subscriberList
139: .toArray(new NodeSubscriber[subscriberList
140: .size()]));
141:
142: publish(this , subscribers, value);
143: }
144: }
145:
146: /*=======================================================================*/
147: /**
148: * Get the current value of the node.
149: *
150: * @return the node's current value
151: */
152: public Object getValue() {
153: return value;
154: }
155:
156: /*=======================================================================*/
157: /**
158: * Called by the registry before this node is linked in to the tree. This
159: * should not be called anywhere else, otherwise undefined behaviour may
160: * ensue. (Translation: <i>Nothing to see here, move along.</i>).
161: */
162: void link(String path) {
163: pathList.addLast(path);
164: }
165:
166: /*=======================================================================*/
167: /**
168: * Called by the registry before this node is unlinked in from the tree.
169: * This should not be called anywhere else, otherwise undefined behaviour
170: * may ensue. (Translation: <i>Nothing to see here, move along.</i>).
171: */
172: void unlink(String path) {
173: pathList.remove(path);
174: }
175:
176: /**
177: * Get the number of paths this node is linked in to. This is used
178: * by the registry-core because it is an error to remove the last
179: * link to a node if it still has children.
180: */
181: int getPathCount() {
182: return pathList.size();
183: }
184:
185: /*=======================================================================*/
186: /**
187: * Get the primary path of this node. If the node is not linked into
188: * the registry tree, then this will be <code>null</code>. Otherwise
189: * this will be the first path this node is linked in to but has not
190: * yet been unlinked from.
191: *
192: * @return a path or <code>null</code>.
193: */
194: public String getPrimaryPath() {
195: if (pathList.size() == 0)
196: return null;
197: return (String) (pathList.getFirst());
198: }
199:
200: /*=======================================================================*/
201: /**
202: * Add the subscriber to the list of registered subscribers, and
203: * immediately publish the current value.
204: *
205: * @param s the subscriber
206: */
207: public void subscribe(NodeSubscriber s) {
208: synchronized (RegistryCore.lock) {
209: subscribers = null;
210: subscriberList.add(s);
211: publish(this , new NodeSubscriber[] { s }, value);
212: }
213: }
214:
215: /*=======================================================================*/
216: /**
217: * Remove the subscriber from the list of registered subscribers.
218: *
219: * @param s the subscriber
220: */
221: public void unsubscribe(NodeSubscriber s) {
222: synchronized (RegistryCore.lock) {
223: subscribers = null;
224: subscriberList.remove(s);
225: }
226: }
227:
228: /*=======================================================================*/
229: /**
230: * Publish the <code>value</code> to the <code>subscribers</code>. Note
231: * that it is important how we pass the current snapshot of the value and
232: * subscriber list, because there may be some period of time between when
233: * this is called and when we actually publish the value (due to the fact
234: * that we serialize publishing values), and either the current value or
235: * the list of subscribers may change between now and when the results
236: * actually get published.
237: */
238: private void publish(Node node, NodeSubscriber[] subscribers,
239: Object value) {
240: if (subscribers.length > 0)
241: worker.invokeLater(new PublishRunnable(node, subscribers,
242: value));
243: }
244:
245: private class PublishRunnable implements Runnable {
246: private Node node;
247: private NodeSubscriber[] subscribers;
248: private Object value;
249:
250: PublishRunnable(Node node, NodeSubscriber[] subscribers,
251: Object value) {
252: this .node = node;
253: this .subscribers = subscribers;
254: this .value = value;
255: }
256:
257: public void run() {
258: for (int i = 0; i < subscribers.length; i++)
259: if (subscriberList.contains(subscribers[i])) // subscriber may have already been removed
260: subscribers[i].publish(node, value); // since publish was scheduled!
261: }
262:
263: public String toString() {
264: return "publishing to " + node;
265: }
266: }
267:
268: private static Worker worker;
269:
270: /* note: load worker reflectively to avoid loading any class with a reference
271: * to swing/AWT if running in a "headless" applicatioin
272: */
273: static {
274: try {
275: worker = (Worker) (Class.forName(System.getProperty(
276: "ti.chimera.registry.workerClass",
277: "ti.chimera.registry.Node$SwingWorker"))
278: .newInstance());
279: } catch (Exception e) {
280: e.printStackTrace();
281: System.exit(-1);
282: }
283: }
284:
285: private interface Worker {
286: public void invokeLater(Runnable r);
287: }
288:
289: public static class SwingWorker implements Worker {
290: public void invokeLater(Runnable r) {
291: javax.swing.SwingUtilities.invokeLater(r);
292: }
293: }
294:
295: public static class NonSwingWorker extends WorkerThread implements
296: Worker {
297: private Runnable lastRunnable;
298:
299: NonSwingWorker() {
300: super ("Registry Worker", 9, 10000);
301: }
302:
303: public void run(Runnable r) {
304: lastRunnable = r;
305: super .run(r);
306: lastRunnable = null; // note: not in finally because we *don't* want it to be cleared if exception is thrown
307: }
308:
309: // XXX we can't actually kill the registry thread, or the UI will
310: // stop working... we need to spawn a background thread to bring
311: // up talkback in or something...
312: public void unhandledException(final Throwable e) {
313: (new Thread() {
314:
315: public void run() {
316: NonSwingWorker.this .getThreadGroup()
317: .uncaughtException(NonSwingWorker.this , e);
318: }
319:
320: }).start();
321: }
322:
323: public void watchdogTimeoutExceeded() {
324: System.err.println("lastRunnable=" + lastRunnable);
325: interrupt();
326: }
327: }
328:
329: /**
330: * For debug
331: */
332: public String toString() {
333: String path = getPrimaryPath();
334: return "[node: " + ((path != null) ? path : "<not linked>")
335: + "]";
336: }
337: }
338:
339: /*
340: * Local Variables:
341: * tab-width: 2
342: * indent-tabs-mode: nil
343: * mode: java
344: * c-indentation-style: java
345: * c-basic-offset: 2
346: * eval: (c-set-offset 'substatement-open '0)
347: * eval: (c-set-offset 'case-label '+)
348: * eval: (c-set-offset 'inclass '+)
349: * eval: (c-set-offset 'inline-open '0)
350: * End:
351: */
|