001: /*
002: * <copyright>
003: *
004: * Copyright 1999-2004 Honeywell Inc
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.logistics.plugin.packer;
028:
029: import org.cougaar.glm.ldm.asset.GLMAsset;
030: import org.cougaar.planning.ldm.PlanningFactory;
031: import org.cougaar.planning.ldm.measure.Mass;
032: import org.cougaar.planning.ldm.plan.AspectScorePoint;
033: import org.cougaar.planning.ldm.plan.AspectType;
034: import org.cougaar.planning.ldm.plan.AspectValue;
035: import org.cougaar.planning.ldm.plan.NewPrepositionalPhrase;
036: import org.cougaar.planning.ldm.plan.NewTask;
037: import org.cougaar.planning.ldm.plan.Preference;
038: import org.cougaar.planning.ldm.plan.PrepositionalPhrase;
039: import org.cougaar.planning.ldm.plan.ScoringFunction;
040: import org.cougaar.planning.ldm.plan.Task;
041: import org.cougaar.util.log.Logger;
042: import org.cougaar.util.log.Logging;
043:
044: import java.util.ArrayList;
045: import java.util.Date;
046: import java.util.Enumeration;
047: import java.util.Iterator;
048: import java.util.Vector;
049:
050: /**
051: * This class provides one of two "wheels" that drive the packing process.
052: * This class provides a Generator that yields a stream of Tasks that
053: * are components of the input tasks and that are sized per the
054: * requirements that are generated by the Filler.
055: * @see Filler
056: */
057: class Sizer {
058: protected long ONE_DAY_MILLIS = 24 * 60 * 60 * 1000;
059:
060: // no two tasks can have arrival dates farther than this time apart and
061: // be on the same milvan
062: protected long MAX_GROUP_DAYS = 2;
063:
064: static public final String TRUE = "True";
065:
066: /**
067: * Unit for task quantity preference
068: */
069: static public final int TONS = 0;
070: static public final int EACHES = 1;
071: static public final int MIN_UNIT = 0;
072: static public final int MAX_UNIT = 1;
073:
074: private static Logger logger = Logging.getLogger(Sizer.class);
075: /**
076: * How much of the quantity remains in the current task
077: */
078: private double _remainder;
079:
080: /**
081: * The tasks we are packing from
082: */
083: private ArrayList _tasks;
084:
085: /**
086: * As we break the current task, of the tasks to be packed,
087: * into right-sized bits, we accumulate component tasks for
088: * a subsidiary workflow in this SList.
089: */
090: private ArrayList _expansionQueue;
091:
092: /**
093: * The task we are currently processing.
094: */
095: private Task _curTask;
096:
097: private GenericPlugin _gp;
098:
099: private int _quantityType;
100:
101: Sizer(ArrayList _packus, GenericPlugin gp, int quantityType) {
102: _tasks = new ArrayList(_packus);
103: _gp = gp;
104: _remainder = 0.0;
105:
106: if ((quantityType < MIN_UNIT) || (quantityType > MAX_UNIT)) {
107: if (logger.isErrorEnabled()) {
108: logger
109: .error("Sizer: invalid quantity unit specified - "
110: + quantityType + " - assuming TONS.");
111: }
112: _quantityType = TONS;
113: } else {
114: _quantityType = quantityType;
115: }
116: }
117:
118: /**
119: * there's more ammo left if the queue has something in it OR
120: * we're on the last task (_curTask) and there's ammo left in it
121: */
122: public boolean moreTasksInQueue() {
123: return !_tasks.isEmpty() || (_remainder > 0);
124: }
125:
126: /**
127: * The Filler will request some amount of quantity from the
128: * Sizer.
129: * This method will return a Task whose quantity is <= to requestedAmt,
130: * or null if there are no more tasks to be packed.
131: */
132: Task provide(double requestedAmt, double earliest, double latest) {
133: Task ret = null;
134:
135: // first, if we've gotten a request, we need to check
136: // to see if the _curTask has anything left in it...
137: if (_remainder == 0.0) {
138: if ((_curTask = getNextTask()) != null) {
139: // there is now one less task in _tasks b/c of getNextTask call
140: Mass taskMass = getTaskMass(_curTask, _quantityType);
141: _remainder = taskMass.getShortTons();
142:
143: if (_expansionQueue != null) {
144: if (logger.isErrorEnabled()) {
145: logger
146: .error("Sizer.provide : ERROR - Expansion queue is not null - we will drop tasks :");
147: }
148: for (Iterator iter = _expansionQueue.iterator(); iter
149: .hasNext();)
150: if (logger.isErrorEnabled()) {
151: logger.error("\t"
152: + ((Task) iter.next()).getUID());
153: }
154: }
155: _expansionQueue = new ArrayList();
156: } else {
157: if (logger.isErrorEnabled()) {
158: logger
159: .error("Sizer.provide : ERROR - no current task...");
160: }
161: return null; // should never happen
162: }
163: }
164:
165: // return null if the current task is either completely before or after
166: // the arrival window of the milvan we're aggregating
167:
168: Preference endDatePref = _curTask
169: .getPreference(AspectType.END_TIME);
170: ScoringFunction sf = endDatePref.getScoringFunction();
171: AspectScorePoint aspStart = sf.getDefinedRange()
172: .getRangeStartPoint();
173: Date taskEarlyDate = new Date((long) aspStart.getValue());
174: if (taskEarlyDate.getTime() > latest) {
175: return null;
176: }
177:
178: AspectScorePoint aspEnd = sf.getDefinedRange()
179: .getRangeEndPoint();
180: Date taskLateDate = new Date((long) aspEnd.getValue());
181: if (taskLateDate.getTime() < earliest) {
182: return null;
183: }
184:
185: long bestArrival = (long) _curTask
186: .getPreferredValue(AspectType.END_TIME);
187:
188: if (earliest > 0 && // if we've ever made a milvan
189: ((bestArrival - earliest) > MAX_GROUP_DAYS
190: * ONE_DAY_MILLIS || (latest - bestArrival) > MAX_GROUP_DAYS
191: * ONE_DAY_MILLIS)) {
192: if (logger.isInfoEnabled()) {
193: logger
194: .info("making a new milvan since task with best arrival of "
195: + new Date(bestArrival)
196: + " outside of (earliest : "
197: + new Date((long) earliest)
198: + "-> latest "
199: + new Date((long) latest) + ") window");
200: }
201: /*
202: System.out.println ("making a new milvan since task with best arrival of " +
203: new Date(bestArrival) + " outside of (earliest : " +
204: new Date((long)earliest) + "-> latest " +
205: new Date((long)latest) + ") window");
206: */
207:
208: return null;
209: }
210:
211: // precondition: _curTask is bound to a Task and remainder >= 0.0
212: // remainder can be == 0.0 because some Plugin developers make such
213: // tasks instead of rescinding requests.
214: //System.out.println(" _remainder is " + _remainder + " requestedAmt " + requestedAmt);
215: // if (_remainder < (requestedAmt + 0.1))
216: if (_remainder <= requestedAmt) {
217: // we are going to use up the entire _curTask and need to
218: // take care of the expansion
219: ret = sizedTask(_curTask, _remainder);
220: _expansionQueue.add(0, ret);
221: makeExpansion(_curTask, _expansionQueue);
222: _expansionQueue = null; // it has been used - we shouldn't try to use it again
223: _remainder = 0.0;
224: } else {
225: ret = sizedTask(_curTask, requestedAmt);
226: _expansionQueue.add(0, ret);
227: _remainder = _remainder - requestedAmt;
228: if (_remainder == 0.0)
229: if (logger.isErrorEnabled()) {
230: logger
231: .error("Sizer.provide : ERROR - We will drop task "
232: + ret.getUID());
233: }
234: }
235: return ret;
236: }
237:
238: /**
239: * This method should only be called after areMoreTasks
240: * has returned true; it does no safety checking itself.
241: */
242: private Task getNextTask() {
243: if (!_tasks.isEmpty()) {
244: Task t = (Task) _tasks.remove(0);
245: return t;
246: } else {
247: return null;
248: }
249: }
250:
251: public int sizedMade = 0;
252:
253: /**
254: * Returns a copy of the input task that is identical in every way,
255: * but whose quantity has been set to size.
256: * This task will <em>not</em> be published yet.
257: */
258: private Task sizedTask(Task t, double size) {
259: PlanningFactory factory = _gp.getGPFactory();
260: NewTask nt = factory.newTask();
261:
262: sizedMade++;
263:
264: nt.setVerb(t.getVerb());
265: nt.setParentTask(t);
266: nt.setDirectObject(factory.createInstance(t.getDirectObject()));
267:
268: // copy the PPs
269: ArrayList preps = new ArrayList();
270: Enumeration e = t.getPrepositionalPhrases();
271: NewPrepositionalPhrase npp;
272: while (e.hasMoreElements()) {
273: npp = factory.newPrepositionalPhrase();
274: PrepositionalPhrase pp = (PrepositionalPhrase) e
275: .nextElement();
276: npp.setPreposition(pp.getPreposition());
277: npp.setIndirectObject(pp.getIndirectObject());
278: preps.add(npp);
279: }
280:
281: npp = factory.newPrepositionalPhrase();
282: // Mark as INTERNAL so we recognize it as an expansion task.
283: npp.setPreposition(GenericPlugin.INTERNAL);
284: npp.setIndirectObject(TRUE);
285: preps.add(npp);
286:
287: //BOZO
288: nt.setPrepositionalPhrases(new Vector(preps).elements());
289:
290: // now copy the preferences, with the exception of quantity...
291: ArrayList prefs = new ArrayList();
292: e = t.getPreferences();
293: while (e.hasMoreElements()) {
294: Preference p = (Preference) e.nextElement();
295: if (p.getAspectType() != AspectType.QUANTITY) {
296: Preference np = factory.newPreference(
297: p.getAspectType(), p.getScoringFunction(), p
298: .getWeight());
299: prefs.add(np);
300: }
301: }
302:
303: AspectValue av = AspectValue.newAspectValue(
304: AspectType.QUANTITY, size);
305: ScoringFunction sf = ScoringFunction.createNearOrBelow(av, 0.1);
306: Preference pref = factory
307: .newPreference(AspectType.QUANTITY, sf);
308: prefs.add(0, pref);
309:
310: //BOZO
311: nt.setPreferences(new Vector(prefs).elements());
312: // TODO delme
313: double provided = nt.getPreferredValue(AspectType.QUANTITY);
314: if (provided != size)
315: if (logger.isInfoEnabled()) {
316: logger.info("requested " + size + " != quantity pref "
317: + provided);
318: }
319: return nt;
320: }
321:
322: /**
323: * Make an expansion by putting all the tasks in subtasks into a new
324: * workflow and making them the workflow of expandme. This can probably
325: * be done using a method on the GenericPlugin.
326: * @see GenericPlugin#createExpansion
327: */
328: private void makeExpansion(Task expandme, ArrayList subtasks) {
329: _gp.createExpansion(subtasks.iterator(), expandme);
330: }
331:
332: /**
333: * Compute the mass associated with the task
334: *
335: */
336: public static Mass getTaskMass(Task task, int quantityUnit) {
337: switch (quantityUnit) {
338: case (EACHES):
339: GLMAsset assetToBePacked = (GLMAsset) task
340: .getDirectObject();
341: if (assetToBePacked.hasPhysicalPG()) {
342: Mass massPerEach = assetToBePacked.getPhysicalPG()
343: .getMass();
344: double taskWeight = task
345: .getPreferredValue(AspectType.QUANTITY)
346: * massPerEach.getShortTons();
347: return new Mass(taskWeight, Mass.SHORT_TONS);
348: } else {
349: if (logger.isDebugEnabled()) {
350: logger
351: .debug("Sizer.getTaskMass: asset - "
352: + assetToBePacked
353: + " - does not have a PhysicalPG. "
354: + "Assuming QUANTITY preference is in stort tons.");
355: }
356: return new Mass(task
357: .getPreferredValue(AspectType.QUANTITY),
358: Mass.SHORT_TONS);
359: }
360:
361: case (TONS):
362: return new Mass(
363: task.getPreferredValue(AspectType.QUANTITY),
364: Mass.SHORT_TONS);
365:
366: default:
367: if (logger.isErrorEnabled()) {
368: logger
369: .error("Sizer: invalid quantity unit specified - "
370: + quantityUnit + " - assuming TONS.");
371: }
372: return new Mass(
373: task.getPreferredValue(AspectType.QUANTITY),
374: Mass.SHORT_TONS);
375: }
376: }
377: }
|