001: /*
002: * <copyright>
003: *
004: * Copyright 1997-2004 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.HashMap;
034: import java.util.HashSet;
035: import java.util.Iterator;
036: import java.util.List;
037: import java.util.Set;
038:
039: import org.cougaar.bootstrap.SystemProperties;
040: import org.cougaar.util.DynamicUnaryPredicate;
041: import org.cougaar.util.Empty;
042: import org.cougaar.util.Enumerator;
043: import org.cougaar.util.UnaryPredicate;
044:
045: /**
046: * A subclass of {@link Subscription} that maintains a {@link
047: * Collection} of blackboard objects matching the filter
048: * predicate, plus {@link ChangeReport}s for the objects changed
049: * since the last transaction.
050: *
051: * @property org.cougaar.core.blackboard.trackUnusedCollections
052: * Periodically log all CollectionSubscriptions that never use their
053: * "real" backing collection and could likely be replaced with
054: * DeltaSubscriptions, which would reduce the memory footprint.
055: * (defaults to false)
056: */
057: public class CollectionSubscription extends Subscription implements
058: Collection {
059:
060: private static final boolean TRACK_UNUSED_COLLECTIONS = SystemProperties
061: .getBoolean("org.cougaar.core.blackboard.trackUnusedCollections");
062:
063: /** The actual (delegate) Container */
064: protected Collection real;
065: private final boolean hasDynamicPredicate;
066: private final HashMap changeMap = new HashMap(13);
067: private boolean usedReal;
068:
069: private static final ObjectTracker USE_TRACKER;
070: static {
071: ObjectTracker u = null;
072: if (TRACK_UNUSED_COLLECTIONS) {
073: u = new ObjectTracker();
074: int period = 30 * 1000;
075: String[] ignored_classes = new String[] {
076: "org.cougaar.core.blackboard.",
077: "org.cougaar.planning.plugin.legacy.PluginAdapter", };
078: u.startThread(period, ignored_classes);
079: }
080: USE_TRACKER = u;
081: }
082:
083: public CollectionSubscription(UnaryPredicate p, Collection c) {
084: super (p);
085: real = c;
086: hasDynamicPredicate = (p instanceof DynamicUnaryPredicate);
087: recordCreation();
088: }
089:
090: public CollectionSubscription(UnaryPredicate p) {
091: super (p);
092: real = new HashSet(13);
093: hasDynamicPredicate = (p instanceof DynamicUnaryPredicate);
094: recordCreation();
095: }
096:
097: /**
098: * Retrieve the underlying Collection backing this Subscription.
099: * When an object is publishAdded and matches the predicate, it
100: * will be added to the Collection. If it is later publishRemoved
101: * (and the predicate still matches), then the object will be
102: * removed from the Collection.
103: * @return the subscription's collection -- use <code>this</code>
104: * instead.
105: */
106: public Collection getCollection() {
107: recordUse();
108: return real;
109: }
110:
111: /**
112: * Return the set of {@link ChangeReport}s for publishChanges made
113: * to the specified object since the last transaction.
114: * <p>
115: * If an object is changed without specifying a ChangeReport
116: * then the "AnonymousChangeReport" is used. Thus the set
117: * is always non-null and contains one or more entries.
118: * <p>
119: * If an object is not changed during the transaction then
120: * this method returns null.
121: * <p>
122: * Illegal to call outside of transaction boundaries.
123: *
124: * @return if the object was changed: a non-null Set of one or
125: * more ChangeReport instances, possibly containing the
126: * AnonymousChangeReport; otherwise null.
127: */
128: public Set getChangeReports(Object o) {
129: checkTransactionOK("hasChanged()");
130: return (Set) changeMap.get(o);
131: }
132:
133: //
134: // implement Collection
135: //
136:
137: /** @return the subscription size */
138: public int size() {
139: recordUse();
140: return real.size();
141: }
142:
143: /** @return {@link #size} == 0 */
144: public boolean isEmpty() {
145: recordUse();
146: return real.isEmpty();
147: }
148:
149: /** @return an Iterator of the subscription contents */
150: public Iterator iterator() {
151: recordUse();
152: return real.iterator();
153: }
154:
155: /** @return true of the subscription contains the object */
156: public boolean contains(Object o) {
157: recordUse();
158: return real.contains(o);
159: }
160:
161: /** @return true of the subscription contains all the objects */
162: public boolean containsAll(Collection c) {
163: recordUse();
164: return real.containsAll(c);
165: }
166:
167: /** @return an array of the subscription contents */
168: public Object[] toArray() {
169: recordUse();
170: return real.toArray();
171: }
172:
173: /** @return an array of the subscription contents */
174: public Object[] toArray(Object[] a) {
175: recordUse();
176: return real.toArray(a);
177: }
178:
179: // semi-bogus collection methods:
180:
181: /**
182: * Use {@link org.cougaar.core.service.BlackboardService#publishAdd} instead.
183: * Add an object to the backing collection, <i>not</i> the blackboard.
184: */
185: public boolean add(Object o) {
186: recordUse();
187: return real.add(o);
188: }
189:
190: /**
191: * Use {@link org.cougaar.core.service.BlackboardService#publishAdd} instead.
192: * Add objects to the backing collection, <i>not</i> the blackboard.
193: */
194: public boolean addAll(Collection c) {
195: recordUse();
196: return real.addAll(c);
197: }
198:
199: /**
200: * Use {@link org.cougaar.core.service.BlackboardService#publishRemove} instead.
201: * Clear the backing collection, <i>not</i> the blackboard.
202: */
203: public void clear() {
204: recordUse();
205: real.clear();
206: }
207:
208: /**
209: * Use {@link org.cougaar.core.service.BlackboardService#publishRemove} instead.
210: * Remove an object from the backing collection, <i>not</i> the blackboard.
211: */
212: public boolean remove(Object o) {
213: recordUse();
214: return real.remove(o);
215: }
216:
217: /**
218: * Use {@link org.cougaar.core.service.BlackboardService#publishRemove} instead.
219: * Remove objects from the backing collection, <i>not</i> the blackboard.
220: */
221: public boolean removeAll(Collection c) {
222: recordUse();
223: return real.removeAll(c);
224: }
225:
226: /**
227: * Use {@link org.cougaar.core.service.BlackboardService#publishRemove} instead.
228: * Retain objects in the backing collection, <i>not</i> the blackboard.
229: */
230: public boolean retainAll(Collection c) {
231: recordUse();
232: return real.retainAll(c);
233: }
234:
235: public boolean equals(Object o) {
236: return (this == o);
237: }
238:
239: public String toString() {
240: return "Subscription of " + real;
241: }
242:
243: /** @return an enumeration of the subscription objects */
244: public Enumeration elements() {
245: recordUse();
246: return new Enumerator(real);
247: }
248:
249: /** @return the first object in the collection. */
250: public Object first() {
251: recordUse();
252: Iterator i = real.iterator();
253: return (i.hasNext()) ? (i.next()) : null;
254: }
255:
256: //
257: // subscriber methods:
258: //
259:
260: /** overrides Subscription.conditionalChange */
261: boolean conditionalChange(Object o, List changes, boolean isVisible) {
262: if (hasDynamicPredicate) {
263: // this logic could be written more tersely, but I think this
264: // is more clear.
265: boolean wasIn = real.contains(o);
266: boolean isIn = predicate.execute(o);
267: if (wasIn) {
268: if (isIn) {
269: // it was and still is in the collection
270: privateChange(o, changes, isVisible);
271: return true;
272: } else {
273: // it was in the collection but isn't any more
274: privateRemove(o, isVisible);
275: privateChange(o, changes, isVisible);
276: return true;
277: }
278: } else {
279: if (isIn) {
280: // it wasn't in the collection but it is now
281: privateAdd(o, isVisible);
282: privateChange(o, changes, isVisible);
283: return true;
284: } else {
285: // is wasn't in and isn't now
286: return false;
287: }
288: }
289: } else {
290: return super .conditionalChange(o, changes, isVisible);
291: }
292: }
293:
294: /** {@link Subscriber} method to add an object */
295: protected void privateAdd(Object o, boolean isVisible) {
296: real.add(o);
297: }
298:
299: /** {@link Subscriber} method to remove an object */
300: protected void privateRemove(Object o, boolean isVisible) {
301: real.remove(o);
302: }
303:
304: /** {@link Subscriber} method to change an object */
305: protected void privateChange(Object o, List changes,
306: boolean isVisible) {
307: if (isVisible) {
308: Set set = (Set) changeMap.get(o);
309: // here we avoid creating a new set instance if it will only
310: // contain the anonymous change report
311: if (changes == AnonymousChangeReport.LIST) {
312: if (set == null) {
313: changeMap.put(o, AnonymousChangeReport.SET);
314: } else if (set != AnonymousChangeReport.SET) {
315: set.add(AnonymousChangeReport.INSTANCE);
316: }
317: } else {
318: if (set == null) {
319: int l = changes.size();
320: changeMap.put(o, set = new HashSet(l));
321: } else if (set == AnonymousChangeReport.SET) {
322: int l = 1 + changes.size();
323: changeMap.put(o, set = new HashSet(l));
324: set.add(AnonymousChangeReport.INSTANCE);
325: }
326: // this should be sufficient because "Set.add" is defined
327: // as only adding an element if it isn't already there.
328: // In any case, it is critical that only the *FIRST* of a
329: // match be included in the set.
330: int size = changes.size();
331: for (int i = 0; i < size; i++) {
332: Object change = changes.get(i);
333: if (change instanceof OverrideChangeReport)
334: set.remove(change);
335: set.add(change);
336: }
337: // set.addAll(changes);
338: }
339: }
340: }
341:
342: /** {@link IncrementalSubscription} method to get the changed enumeration */
343: protected Enumeration privateGetChangedList() {
344: if (changeMap.isEmpty())
345: return Empty.enumeration;
346: return new Enumerator(changeMap.keySet());
347: }
348:
349: /** {@link IncrementalSubscription} method to get the changed collection */
350: protected Collection privateGetChangedCollection() {
351: if (changeMap.isEmpty())
352: return Collections.EMPTY_SET;
353: return changeMap.keySet();
354: }
355:
356: /** {@link Subscriber} method to reset the ChangeReports map */
357: protected void resetChanges() {
358: super .resetChanges(); // propagate reset
359: changeMap.clear();
360: }
361:
362: private void recordCreation() {
363: if (TRACK_UNUSED_COLLECTIONS) {
364: USE_TRACKER.add(this );
365: if (hasDynamicPredicate
366: || (this instanceof DeltaSubscription)) {
367: recordUse();
368: }
369: }
370: }
371:
372: private void recordUse() {
373: if (TRACK_UNUSED_COLLECTIONS && !usedReal) {
374: usedReal = true;
375: USE_TRACKER.remove(this);
376: }
377: }
378: }
|