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.mlm.debug.ui;
028:
029: import java.awt.event.ActionEvent;
030: import java.awt.event.ActionListener;
031: import java.text.DateFormat;
032: import java.util.Calendar;
033: import java.util.Date;
034: import java.util.Enumeration;
035: import java.util.GregorianCalendar;
036: import java.util.Hashtable;
037: import java.util.TimeZone;
038: import java.util.Vector;
039:
040: import org.cougaar.core.blackboard.IncrementalSubscription;
041: import org.cougaar.core.mts.MessageAddress;
042: import org.cougaar.glm.ldm.asset.PhysicalAsset;
043: import org.cougaar.planning.ldm.asset.AggregateAsset;
044: import org.cougaar.planning.ldm.asset.Asset;
045: import org.cougaar.planning.ldm.plan.Allocation;
046: import org.cougaar.planning.ldm.plan.AllocationResult;
047: import org.cougaar.planning.ldm.plan.AspectType;
048: import org.cougaar.planning.ldm.plan.PlanElement;
049: import org.cougaar.planning.ldm.plan.Schedule;
050: import org.cougaar.planning.ldm.plan.ScheduleElement;
051: import org.cougaar.planning.ldm.plan.ScheduleElementImpl;
052: import org.cougaar.util.UnaryPredicate;
053:
054: /** Creates the values for a bar graph for displaying assets vs. time.
055: */
056:
057: public class UISchedule implements UIBarGraphSource, UISubscriber {
058: private UIPlugin uiPlugin;
059: private static long MSECS_PER_HOUR = 3600000;
060: private static long MSECS_PER_DAY = MSECS_PER_HOUR * 24;
061: private Vector listeners;
062: private UITimeLine uiTimeLine;
063: private Date startDate;
064: private int numberOfXIntervals;
065: private String xLegend;
066: private String xLabels[];
067: private int numberOfYIntervals = 0;
068: private String yLegend;
069: private String yLabels[];
070: private String legend[];
071: private int intervalUnits; // Calendar.YEAR, DAY_OF_YEAR, HOUR_OF_DAY
072: // for each bar, the y-value for the xth interval,
073: // i.e. the quantity of an asset scheduled for each time interval
074: private int values[][] = null;
075: private Hashtable assetScheduleDictionary; // for multiple assets
076: private String assetName = null; // for single assets
077: boolean singleAsset; // flag used throughout to distinguish plotting single vs. multiple assets
078: private Vector planElements = new Vector(10);
079:
080: /** Create the information for a bar graph of scheduled assets,
081: which are assets allocated by the specified cluster for the
082: specified plan.
083: The display consists of a set of vertical bars for each asset;
084: the y-axis represents quantity of the asset, the x-axis represents time.
085: @param uiPlugin this user interface plugin
086: @param planName the name of the plan for which to display scheduled assets
087: @param clusterId the cluster for which to display scheduled assets
088: @param assetName name of asset, null to graph all assets
089: @exception UINoPlanException thrown when the plan does not exist
090: */
091:
092: public UISchedule(UIPlugin uiPlugin, String planName,
093: MessageAddress clusterId, String assetName, Object listener)
094: throws UINoPlanException {
095: if (assetName != null) {
096: singleAsset = true;
097: this .assetName = assetName;
098: } else {
099: singleAsset = false;
100: }
101: listeners = new Vector(1);
102: listeners.addElement(listener);
103: this .uiPlugin = uiPlugin;
104: }
105:
106: /** Can't start subscription in constructor, because you could
107: get a subscriptionChanged before the UIBarGraphDisplay is ready.
108: */
109:
110: public void startSubscription() {
111: uiPlugin.subscribe(this , planElementPredicate());
112: }
113:
114: private static UnaryPredicate planElementPredicate() {
115: return new UnaryPredicate() {
116: public boolean execute(Object o) {
117: //System.out.println("Predicate called with: " + o.toString());
118: return (o instanceof PlanElement);
119: }
120: };
121: }
122:
123: public synchronized void subscriptionChanged(
124: IncrementalSubscription container) {
125: // System.out.println("Container changed");
126: Enumeration added = container.getAddedList();
127: while (added.hasMoreElements())
128: planElements.addElement(added.nextElement());
129: Enumeration removed = container.getRemovedList();
130: while (removed.hasMoreElements())
131: planElements.removeElement(removed.nextElement());
132: recomputeAssets();
133: }
134:
135: /** Get all the plan elements and then get all the assets and schedules.
136: There is a hash table entry for each asset name which contains
137: a vector of scheduleitems; each scheduleitem
138: is a schedule from a single allocation and a quantity
139: (which is 1 for single assets and asset.getCount()) for aggregate assets.
140: This fills in the vectors of scheduleitems by recording the schedules
141: in all the plan elements which are allocations
142: and which allocate physical assets (not clusters).
143: It also computes the earliest and latest dates in schedules;
144: these are used to lay out the time line.
145: */
146:
147: private void computeAssetSchedules() {
148: int quantity;
149: Date endDate = new Date(); // init value; it's set from first schedule
150: startDate = endDate; // init for timeline
151: boolean datesInitted = false; // set true when we see first real schedule
152: assetScheduleDictionary = new Hashtable(10);
153: int nAssets = 0; // number of different assets
154: for (int j = 0; j < planElements.size(); j++) {
155: PlanElement planElement = (PlanElement) planElements
156: .elementAt(j);
157: if (!(planElement instanceof Allocation))
158: continue; // not an allocation
159: Asset asset = (Asset) ((Allocation) planElement).getAsset();
160: if (!((asset instanceof PhysicalAsset) || (asset instanceof AggregateAsset)))
161: continue; // not a physical or aggregate asset
162: String this AssetName = "";
163: if (asset instanceof PhysicalAsset)
164: this AssetName = UIAsset.getDescription(asset);
165: else if (asset instanceof AggregateAsset) {
166: AggregateAsset aa = (AggregateAsset) asset;
167: if (aa.getAsset() == null)
168: continue; // ignore aggregate assets with null property
169: this AssetName = UIAsset.getDescription(aa.getAsset());
170:
171: }
172:
173: if ((this AssetName == null) || (this AssetName.equals("")))
174: continue; // ignore assets with null or empty names
175: if ((singleAsset) && (!(this AssetName.equals(assetName))))
176: continue; // not the asset we're interested in
177: ScheduleElement schedule = null;
178: AllocationResult ar = planElement.getReportedResult();
179: if (ar != null) {
180: double resultstart = ar.getValue(AspectType.START_TIME);
181: double resultend = ar.getValue(AspectType.END_TIME);
182: schedule = new ScheduleElementImpl(new Date(
183: ((long) resultstart)), new Date(
184: ((long) resultend)));
185: }
186: if (schedule == null)
187: continue; // ignore assets without schedules
188: quantity = 1;
189: if (asset instanceof AggregateAsset) {
190: quantity = (int) ((AggregateAsset) asset).getQuantity();
191: }
192: ScheduleElementItem scheduleItem = new ScheduleElementItem(
193: schedule, quantity);
194: if (!datesInitted) {
195: datesInitted = true;
196: startDate = schedule.getStartDate();
197: endDate = schedule.getEndDate();
198: } else {
199: if (schedule.getStartDate().before(startDate))
200: startDate = schedule.getStartDate();
201: if (schedule.getEndDate().after(endDate))
202: endDate = schedule.getEndDate();
203: }
204: Vector assetSchedules = (Vector) assetScheduleDictionary
205: .get(this AssetName);
206: if (assetSchedules == null) {
207: assetSchedules = new Vector(10);
208: assetSchedules.addElement(scheduleItem);
209: assetScheduleDictionary.put(this AssetName,
210: assetSchedules);
211: nAssets++;
212: } else {
213: assetSchedules.addElement(scheduleItem);
214: }
215: }
216:
217: // determine if the timeline interval should be years or days or hours
218: // and round start/end back/forward to year, day or hour boundaries
219: GregorianCalendar startCalendar = new GregorianCalendar(
220: TimeZone.getTimeZone("GMT"));
221: startCalendar.setTime(startDate);
222: GregorianCalendar endCalendar = new GregorianCalendar(TimeZone
223: .getTimeZone("GMT"));
224: endCalendar.setTime(endDate);
225: int startYear = startCalendar.get(Calendar.YEAR);
226: int endYear = endCalendar.get(Calendar.YEAR);
227: if ((endYear - startYear) > 1) {
228: intervalUnits = Calendar.YEAR;
229: int year = startCalendar.get(Calendar.YEAR);
230: startCalendar.clear();
231: startCalendar.set(year, 0, 0);
232: endCalendar.add(Calendar.YEAR, 1);
233: year = endCalendar.get(Calendar.YEAR);
234: endCalendar.clear();
235: endCalendar.set(year, 0, 0);
236: } else {
237: long endTime = endCalendar.getTime().getTime();
238: long startTime = startCalendar.getTime().getTime();
239: int numberOfIntervals = (int) ((endTime - startTime) / MSECS_PER_DAY) + 1;
240: if (numberOfIntervals > 2) { // use days
241: intervalUnits = Calendar.DAY_OF_YEAR;
242: int year = startCalendar.get(Calendar.YEAR);
243: int month = startCalendar.get(Calendar.MONTH);
244: int day = startCalendar.get(Calendar.DAY_OF_MONTH);
245: startCalendar.clear();
246: startCalendar.set(year, month, day);
247: endCalendar.add(Calendar.DAY_OF_YEAR, 1);
248: year = endCalendar.get(Calendar.YEAR);
249: month = endCalendar.get(Calendar.MONTH);
250: day = endCalendar.get(Calendar.DAY_OF_MONTH);
251: endCalendar.clear();
252: endCalendar.set(year, month, day);
253: } else { // use hours
254: intervalUnits = Calendar.HOUR_OF_DAY;
255: int year = startCalendar.get(Calendar.YEAR);
256: int month = startCalendar.get(Calendar.MONTH);
257: int day = startCalendar.get(Calendar.DAY_OF_MONTH);
258: int hour = startCalendar.get(Calendar.HOUR_OF_DAY);
259: startCalendar.clear();
260: startCalendar.set(year, month, day, hour, 0);
261: endCalendar.add(Calendar.HOUR_OF_DAY, 1);
262: year = endCalendar.get(Calendar.YEAR);
263: month = endCalendar.get(Calendar.MONTH);
264: day = endCalendar.get(Calendar.DAY_OF_MONTH);
265: hour = endCalendar.get(Calendar.HOUR_OF_DAY);
266: endCalendar.clear();
267: endCalendar.set(year, month, day, hour, 0);
268: }
269: }
270: startDate = startCalendar.getTime();
271: endDate = endCalendar.getTime();
272: // create a time line for the x-axis
273: uiTimeLine = new UITimeLine(startDate, endDate, intervalUnits);
274: // set numberOfXIntervals, xLegend, xLabels from the timeline
275: numberOfXIntervals = uiTimeLine.getNumberOfIntervals();
276: xLegend = uiTimeLine.getIntervalUnitsName() + " starting at "
277: + getDateLabel(startDate);
278: xLabels = uiTimeLine.getLabels();
279:
280: // init empty displays for single assets
281: if ((singleAsset) && (nAssets == 0)) {
282: legend = new String[1]; // legend is the name of this asset
283: legend[0] = assetName;
284: values = new int[0][];
285: } else {
286: // number of vertical bars at each interval is the number of assets
287: values = new int[nAssets][];
288: Enumeration assetNames = assetScheduleDictionary.keys();
289: legend = new String[nAssets];
290: int i = 0;
291: while (assetNames.hasMoreElements()) {
292: String assetName = (String) assetNames.nextElement();
293: values[i] = getAssetSchedule(assetName);
294: legend[i++] = assetName;
295: }
296: }
297:
298: // finally compute y labels which are just numbers of assets
299: yLabels = new String[numberOfYIntervals];
300: for (int i = 1; i <= numberOfYIntervals; i++)
301: yLabels[i - 1] = String.valueOf(i);
302: // set the yLegend
303: yLegend = "Assets";
304: }
305:
306: /** Given the name of an asset, return the number of that
307: asset used for each interval in the overall schedule.
308: */
309:
310: private int[] getAssetSchedule(String assetName) {
311: long intervalMSecs = 0;
312: Vector assetSchedules = (Vector) assetScheduleDictionary
313: .get(assetName);
314: int totals[] = new int[numberOfXIntervals];
315: long intervalStart = startDate.getTime();
316: if (intervalUnits == Calendar.HOUR_OF_DAY)
317: intervalMSecs = 3600000;
318: else if (intervalUnits == Calendar.DAY_OF_YEAR)
319: intervalMSecs = 86400000;
320: else if (intervalUnits == Calendar.YEAR)
321: intervalMSecs = 86400000 * 365; // roughly
322: long intervalEnd = intervalStart + intervalMSecs;
323: for (int j = 0; j < numberOfXIntervals; j++) {
324: int total = 0;
325: for (int i = 0; i < assetSchedules.size(); i++) {
326: ScheduleItem scheduleItem = (ScheduleItem) assetSchedules
327: .elementAt(i);
328: Schedule schedule = scheduleItem.schedule;
329: long scheduleStart = schedule.getStartTime();
330: long scheduleEnd = schedule.getEndTime();
331: if (((intervalStart <= scheduleStart) && (intervalEnd > scheduleStart))
332: || ((intervalStart <= scheduleEnd) && (intervalEnd > scheduleEnd))) {
333: total = total + scheduleItem.quantity;
334: }
335: }
336: totals[j] = total;
337: numberOfYIntervals = Math.max(numberOfYIntervals, total);
338: intervalStart = intervalStart + intervalMSecs;
339: intervalEnd = intervalEnd + intervalMSecs;
340: }
341: return totals;
342: }
343:
344: /** Return a string for the date and time in GMT.
345: */
346:
347: private String getDateLabel(Date date) {
348: DateFormat dateFormat = DateFormat.getDateTimeInstance(
349: DateFormat.LONG, DateFormat.SHORT);
350: dateFormat.setTimeZone(TimeZone.getTimeZone("GMT"));
351: return dateFormat.format(date);
352: }
353:
354: /** Number of intervals (years, days, or hours) in the schedule.
355: @return number of years, days or hours in the schedule
356: */
357:
358: public int getNumberOfXIntervals() {
359: return numberOfXIntervals;
360: }
361:
362: /** (Years, days or hours) starting at (start date)
363: @return "Years" "Days" or "Hours" "starting at" (start date)
364: */
365:
366: public String getXLegend() {
367: return xLegend;
368: }
369:
370: /** Years, days or hours.
371: @return numeric labels for years, days or hours
372: */
373:
374: public String[] getXLabels() {
375: return xLabels;
376: }
377:
378: /** Maximum number of assets assigned in any one time interval.
379: @return maximum number of assets assigned in a time interval
380: */
381:
382: public int getNumberOfYIntervals() {
383: return numberOfYIntervals;
384: }
385:
386: /** "Assets"
387: @return "Assets"
388: */
389:
390: public String getYLegend() {
391: return yLegend;
392: }
393:
394: /** Quantities of assets.
395: @return numeric labels for quantities of assets
396: */
397:
398: public String[] getYLabels() {
399: return yLabels;
400: }
401:
402: /** Names of assets.
403: @return names of assets
404: */
405:
406: public String[] getLegend() {
407: return legend;
408: }
409:
410: /** The quantity of each asset scheduled for each time interval.
411: @return for each asset, the quantity scheduled for each time interval
412: */
413:
414: public int[][] getValues() {
415: return values;
416: }
417:
418: /** Whether or not to make the bars in the bar graph contiguous.
419: @return for single assets, return true; for multiple assets, return false
420: */
421:
422: public boolean getContiguous() {
423: return singleAsset;
424: }
425:
426: /** Listen for changes in the assets scheduled by the cluster.
427: @param listener object to notify when scheduled assets change
428: */
429:
430: public void registerListener(ActionListener listener) {
431: listeners.addElement(listener);
432: }
433:
434: /** Handle added, deleted or changed events on the plan elements
435: and notify listeners of the change.
436: The UIBarGraphDisplay object listens for changes
437: and invokes methods in the UIBarGraph object to get
438: the updated values from this object and repaint the graph.
439: @param e event (new, changed, or deleted object)
440: */
441:
442: /** Notify listeners that scheduled assets have changed.
443: */
444:
445: private void recomputeAssets() {
446: computeAssetSchedules();
447: for (int i = 0; i < listeners.size(); i++) {
448: ActionListener listener = (ActionListener) listeners
449: .elementAt(i);
450: //System.out.println("Listener notified");
451: listener.actionPerformed(new ActionEvent(this , 0, ""));
452: }
453: }
454:
455: /** Called to force an update of the asset schedules.
456: */
457:
458: public void update() {
459: recomputeAssets();
460: }
461: }
462:
463: class ScheduleItem {
464: Schedule schedule;
465: int quantity;
466:
467: public ScheduleItem(Schedule schedule, int quantity) {
468: this .schedule = schedule;
469: this .quantity = quantity;
470: }
471:
472: }
473:
474: class ScheduleElementItem {
475: ScheduleElement se;
476: int quantity;
477:
478: public ScheduleElementItem(ScheduleElement scheduleelem,
479: int quantity) {
480: se = scheduleelem;
481: this.quantity = quantity;
482: }
483: }
|