001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2007 BBNT Solutions, LLC
005: * under sponsorship of the Defense Advanced Research Projects
006: * Agency (DARPA).
007: *
008: * You can redistribute this software and/or modify it under the
009: * terms of the Cougaar Open Source License as published on the
010: * Cougaar Open Source Website (www.cougaar.org).
011: *
012: * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
013: * "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
014: * LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
015: * A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
016: * OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
017: * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
018: * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
019: * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
020: * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
021: * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
022: * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
023: *
024: * </copyright>
025: */
026:
027: package org.cougaar.core.blackboard;
028:
029: import java.util.ArrayList;
030: import java.util.Collection;
031: import java.util.Collections;
032: import java.util.Enumeration;
033: import java.util.Iterator;
034: import java.util.List;
035: import java.util.Set;
036:
037: import org.cougaar.util.Empty;
038: import org.cougaar.util.Enumerator;
039: import org.cougaar.util.UnaryPredicate;
040:
041: /**
042: * A subscription that queues objects, such as Alarms and other non-blackboard
043: * callbacks, for processing in the plugin's "execute()" thread.
044: * <p>
045: * TodoSubscriptions are typically used for Alarm callbacks, for example:
046: * <pre>
047: * public class MyPlugin extends ComponentPlugin {
048: *
049: * private TodoSubscription expiredAlarms;
050: *
051: * protected void setupSubscriptions() {
052: * expiredAlarms = (TodoSubscription)
053: * blackboard.subscribe(new TodoSubscription("x"));
054: *
055: * // wake up in 10 seconds
056: * getAlarmService().addRealTimeAlarm(
057: * new MyAlarm(System.currentTimeMillis() + 10000));
058: * }
059: *
060: * protected void execute() {
061: * if (expiredAlarms.hasChanged()) {
062: * System.out.println("Due alarms: "+expiredAlarms.getAddedCollection());
063: * }
064: * }
065: *
066: * private class MyAlarm extends AlarmBase {
067: * // optionally add fields here, e.g. data or a Runnable
068: * public MyAlarm(long time) { super(time); }
069: *
070: * // don't do work in the "onExpire()" method, since it's the
071: * // AlarmService's callback thread. Instead, put this alarm on our
072: * // "todo" queue, which will asynchronously call our "execute()".
073: * public void onExpire() { expiredAlarms.add(this); }
074: * }
075: * }
076: * </pre>
077: * <p>
078: * This is analogous to Swing's "invokeLater" pattern, for example:
079: * <pre>
080: * SwingUtilities.invokeLater(new Runnable() {
081: * public void run() { ... }
082: * });
083: * </pre>
084: * where the Runnable is put on a queue and will be "run()" in the Swing
085: * thread.
086: */
087: public class TodoSubscription extends Subscription {
088:
089: // this is never used, but is required by our super class
090: private static final UnaryPredicate FALSE_P = new UnaryPredicate() {
091: public boolean execute(Object o) {
092: return false;
093: }
094: };
095:
096: private final String name;
097: private Collection active = null;
098:
099: /**
100: * Create a TodoSubscription with the given non-null, unique name.
101: * <p>
102: * The name can be an arbitrary, non-null name, so long as the set of
103: * names is unique within each plugin instance.
104: * <p>
105: * We need the name to support agent mobility and persistence in cases where
106: * a plugin has more than one TodoSubscription.
107: * <p>
108: * For example, say a plugin has two TodoSubscriptions:<pre>
109: * protected void setupSubscriptions() {
110: * alpha = (TodoSubscription) blackboard.subscribe(new TodoSubscription("x"));
111: * beta = (TodoSubscription) blackboard.subscribe(new TodoSubscription("y"));
112: * }
113: * </pre>
114: * and we persist just <i>after</i> calling:<pre>
115: * alpha.add(new Foo(1234));
116: * </pre>
117: * but <i>before</i> we've had a chance to "execute()". This pending object
118: * will be persisted as a pending "inbox envelope" and rehydrated when we
119: * restart the agent. Our "setupSubscriptions()" will be called again, and we
120: * want to make sure that the object is put on the "alpha"
121: * {@link #getAddedCollection} and not on "beta".
122: * <p>
123: * We need the name to match the rehydrated "inbox envelope" with the above<pre>
124: * ... new TodoSubscription("x") ...
125: * </pre>
126: * since we persist the envelopes, <i>not</i> the subscription instances.
127: * Besides, the "new TodoSubscription" is a brand new subscription instance,
128: * so without the name, we'd have no way to figure out that these two
129: * instances represent the same "todo".
130: */
131: public TodoSubscription(String name) {
132: super (FALSE_P);
133: this .name = name;
134: if (name == null) {
135: throw new IllegalArgumentException("Null name");
136: }
137: }
138:
139: /**
140: * Create the backing collection for reuse in {@link #getAddedCollection},
141: * which defaults to an ArrayList.
142: * <p>
143: * Another useful option is a LinkedHashSet, which filters out duplicates.
144: */
145: protected Collection createCollection() {
146: return new ArrayList(5);
147: }
148:
149: /**
150: * Add an object to the queue of pending objects that will be visible in the
151: * next blackboard transaction's {@link #getAddedCollection}, and request an
152: * asynchronous plugin "execute()" cycle.
153: * <p>
154: * This is analogous to a blackboard "publishAdd", except that the object is
155: * only visible to this subscription and will not be persisted.
156: *
157: * @param o the object to put on the queue, which can be any non-null object
158: * (either data or a Runnable)
159: */
160: public void add(Object o) {
161: if (o == null)
162: throw new NullPointerException();
163: addLater(o, false);
164: }
165:
166: /** @see #add(Object) */
167: public void addAll(Collection c) {
168: if (!c.isEmpty()) {
169: addLater(c, true);
170: }
171: }
172:
173: private void addLater(Object o, boolean isBulk) {
174: subscriber
175: .receiveEnvelopes(
176: Collections.singletonList(new TodoEnvelope(
177: name, o, isBulk)), true);
178: }
179:
180: private boolean addNow(Object o, boolean isBulk) {
181: if (o == null)
182: return false;
183: if (active == null) {
184: active = createCollection();
185: }
186: if (!isBulk)
187: return active.add(o);
188: boolean ret = false;
189: Collection c = (Collection) o;
190: for (Iterator iter = c.iterator(); iter.hasNext();) {
191: Object oi = iter.next();
192: if (oi != null) {
193: ret |= active.add(oi);
194: }
195: }
196: return ret;
197: }
198:
199: /**
200: * @return an enumeration of the objects that have been added
201: * since the last transaction.
202: */
203: public Enumeration getAddedList() {
204: checkTransactionOK("getAddedList()");
205: if (active == null || active.isEmpty())
206: return Empty.enumeration;
207: return new Enumerator(active);
208: }
209:
210: /**
211: * @return a possibly empty collection of objects that have been
212: * added since the last transaction. Will not return null.
213: */
214: public Collection getAddedCollection() {
215: return (active == null ? Collections.EMPTY_SET : active);
216: }
217:
218: protected void resetChanges() {
219: super .resetChanges();
220: if (active != null) {
221: active.clear();
222: }
223: }
224:
225: /** For infrastructure use only */
226: public void fill(Envelope envelope) {
227: // we only care about rehydrated "pending envelopes"
228: apply(envelope);
229: }
230:
231: /** For infrastructure use only */
232: public boolean apply(Envelope envelope) {
233: if (envelope instanceof TodoEnvelope) {
234: TodoEnvelope te = (TodoEnvelope) envelope;
235: if (name.equals(te.getName())) {
236: boolean ret = addNow(te.getObject(), te.isBulk);
237: if (ret) {
238: setChanged(true);
239: }
240: return ret;
241: }
242: }
243: return false;
244: }
245:
246: // we override "apply", so these methods are never called.
247: protected void privateAdd(Object o, boolean isVisible) {
248: die();
249: }
250:
251: protected void privateRemove(Object o, boolean isVisible) {
252: die();
253: }
254:
255: protected void privateChange(Object o, List changes,
256: boolean isVisible) {
257: die();
258: }
259:
260: private void die() {
261: throw new InternalError();
262: }
263:
264: private static final class TodoEnvelope extends Envelope {
265:
266: private final String name;
267: private final Object o;
268: private final boolean isBulk;
269:
270: public TodoEnvelope(String name, Object o, boolean isBulk) {
271: this .name = name;
272: this .o = o;
273: this .isBulk = isBulk;
274: }
275:
276: public Envelope newInstance() {
277: return new TodoEnvelope(name, o, isBulk);
278: }
279:
280: public String getName() {
281: return name;
282: }
283:
284: public Object getObject() {
285: return o;
286: }
287:
288: public boolean isBulk() {
289: return isBulk;
290: }
291:
292: public String toString() {
293: return "TodoEnvelope for " + name;
294: }
295: }
296: }
|