1 /*
2 * Project.cpp - TaskJuggler
3 *
4 * Copyright (c) 2001, 2002, 2003, 2004, 2005, 2006
5 * by Chris Schlaeger <cs@kde.org>
6 * Copyright (c) 2011 Dag Andersen <danders@get2net.dk>
7 *
8 * This program is free software; you can redistribute it and/or modify
9 * it under the terms of version 2 of the GNU General Public License as
10 * published by the Free Software Foundation.
11 *
12 * $Id$
13 */
14
15 #define tjDebug qDebug
16
17 // clazy:excludeall=qstring-arg
18 #include "Project.h"
19
20 #include <stdlib.h>
21 #include <QList>
22 #include <QString>
23 #include <QStringList>
24 #include <QDebug>
25
26 #include <KLocalizedString>
27
28 #include "TjMessageHandler.h"
29 #include "Scenario.h"
30 #include "Shift.h"
31 // #include "Account.h"
32 #include "Resource.h"
33 /*#include "HTMLTaskReport.h"
34 #include "HTMLResourceReport.h"
35 #include "HTMLAccountReport.h"
36 #include "HTMLWeeklyCalendar.h"
37 #include "HTMLStatusReport.h"
38 #include "CSVTaskReport.h"
39 #include "CSVResourceReport.h"
40 #include "CSVAccountReport.h"
41 #include "ExportReport.h"
42 #include "ReportXML.h"*/
43 #include "UsageLimits.h"
44 #include "CustomAttributeDefinition.h"
45
46 DebugController DebugCtrl;
47
48 namespace TJ
49 {
50
Project()51 Project::Project() :
52 QObject(),
53 start(0),
54 end(0),
55 now(0),
56 allowRedefinitions(false),
57 weekStartsMonday(true),
58 name(),
59 version(),
60 copyright(),
61 customer(),
62 timeZone(),
63 timeFormat("%Y-%m-%d %H:%M"),
64 shortTimeFormat("%H:%M"),
65 // currency(),
66 // currencyDigits(3),
67 // numberFormat("-", "", ",", ".", 1),
68 // currencyFormat("(", ")", ",", ".", 0),
69 priority(500),
70 minEffort(0.0),
71 resourceLimits(0),
72 rate(0.0),
73 dailyWorkingHours(8.0),
74 yearlyWorkingDays(260.714),
75 workingHours(),
76 scheduleGranularity(ONEHOUR),
77 allowedFlags(),
78 projectIDs(),
79 currentId(),
80 maxErrors(0),
81 // journal(),
82 vacationList(),
83 scenarioList(),
84 taskList(),
85 resourceList(),
86 // accountList(),
87 shiftList(),
88 originalTaskList(),
89 originalResourceList(),
90 // originalAccountList(),
91 taskAttributes(),
92 resourceAttributes(),
93 // accountAttributes(),
94 // xmlreport(0),
95 // reports(),
96 // interactiveReports(),
97 sourceFiles(),
98 breakFlag(false)
99 {
100 qDebug()<<"Project:"<<this;
101 /* Pick some reasonable initial number since we don't know the
102 * project time frame yet. */
103 initUtility(20000);
104
105 // vacationList.setAutoDelete(true);
106 // accountAttributes.setAutoDelete(true);
107 // taskAttributes.setAutoDelete(true);
108 // resourceAttributes.setAutoDelete(true);
109 // reports.setAutoDelete(true);
110
111 new Scenario(this, "plan", "Plan", 0);
112 scenarioList.createIndex(true);
113 scenarioList.createIndex(false);
114 foreach(CoreAttributes *s, scenarioList) {
115 qDebug()<<"Project:"<<static_cast<CoreAttributes*>(s)<<static_cast<CoreAttributes*>(s)->getName()<<static_cast<CoreAttributes*>(s)->getSequenceNo();
116 }
117 setNow(time(0));
118
119 /* Initialize working hours with default values that match the Monday -
120 * Friday 9 - 6 (with 1 hour lunch break) pattern used by many western
121 * countries. */
122 // Sunday
123 workingHours[0] = new QList<Interval*>();
124 // workingHours[0]->setAutoDelete(true);
125
126 for (int i = 1; i < 6; ++i)
127 {
128 workingHours[i] = new QList<Interval*>();
129 // workingHours[i]->setAutoDelete(true);
130 workingHours[i]->append(new Interval(9 * ONEHOUR, 12 * ONEHOUR - 1));
131 workingHours[i]->append(new Interval(13 * ONEHOUR, 18 * ONEHOUR - 1));
132 }
133
134 // Saturday
135 workingHours[6] = new QList<Interval*>();
136 // workingHours[6]->setAutoDelete(true);
137 }
138
~Project()139 Project::~Project()
140 {
141 qDebug()<<"~Project:"<<this;
142 taskList.deleteContents();
143 resourceList.deleteContents();
144 Resource::deleteStaticData();
145
146 // accountList.deleteContents();
147 shiftList.deleteContents();
148 scenarioList.deleteContents();
149
150 delete resourceLimits;
151
152 // Remove support for 1.0 XML reports for next major release. */
153 // delete xmlreport;
154
155 for (int i = 0; i < 7; ++i) {
156 while (! workingHours[i]->isEmpty()) {
157 delete workingHours[i]->takeFirst();
158 }
159 delete workingHours[i];
160 }
161 exitUtility();
162
163 qDebug()<<"~Project:"<<this;
164 }
165
166 // void
167 // Project::addSourceFile(const QString& f)
168 // {
169 // if (sourceFiles.find(f) == sourceFiles.end())
170 // sourceFiles.append(f);
171 // }
172 //
173 // QStringList
174 // Project::getSourceFiles() const
175 // {
176 // return sourceFiles;
177 // }
178
179 void
setProgressInfo(const QString & i)180 Project::setProgressInfo(const QString& i)
181 {
182 emit updateProgressInfo(i);
183 }
184
185 void
setProgressBar(int i,int of)186 Project::setProgressBar(int i, int of)
187 {
188 emit updateProgressBar(i, of);
189 }
190
191 bool
setTimeZone(const QString & tz)192 Project::setTimeZone(const QString& tz)
193 {
194 if (!setTimezone(tz.toLocal8Bit()))
195 return false;
196
197 timeZone = tz;
198 return true;
199 }
200
201 Scenario*
getScenario(int sc) const202 Project::getScenario(int sc) const
203 {
204 return static_cast<Scenario*>(scenarioList.value(sc));
205 }
206
207 QString
getScenarioName(int sc) const208 Project::getScenarioName(int sc) const
209 {
210 Scenario *s = getScenario(sc);
211 return s ? s->getName() : QString();
212 }
213
214 QString
getScenarioId(int sc) const215 Project::getScenarioId(int sc) const
216 {
217 Scenario *s = getScenario(sc);
218 return s ? s->getId() : QString();
219 }
220
221 int
getScenarioIndex(const QString & id) const222 Project::getScenarioIndex(const QString& id) const
223 {
224 return scenarioList.getIndex(id);
225 }
226
227 void
setNow(time_t n)228 Project::setNow(time_t n)
229 {
230 /* Align 'now' time to timing resolution. If the resolution is
231 * changed later, this has to be done again. */
232 now = (n / scheduleGranularity) * scheduleGranularity;
233 }
234
235 void
setWorkingHours(int day,const QList<Interval * > & l)236 Project::setWorkingHours(int day, const QList< Interval* >& l)
237 {
238 if (day < 0 || day > 6)
239 qFatal("day out of range");
240 delete workingHours[day];
241
242 // Create a deep copy of the interval list.
243 workingHours[day] = new QList<Interval*>;
244 // workingHours[day]->setAutoDelete(true);
245 foreach(Interval *i, l) {
246 workingHours[day]->append(new Interval(*i));
247
248 }
249 }
250
251 bool
addId(const QString & id,bool changeCurrentId)252 Project::addId(const QString& id, bool changeCurrentId)
253 {
254 if (projectIDs.indexOf(id) != -1)
255 return false;
256 else
257 projectIDs.append(id);
258
259 if (changeCurrentId)
260 currentId = id;
261
262 return true;
263 }
264
265 QString
getIdIndex(const QString & i) const266 Project::getIdIndex(const QString& i) const
267 {
268 int idx;
269 if ((idx = projectIDs.indexOf(i)) == -1)
270 return QString("?");
271 QString idxStr;
272 do
273 {
274 idxStr = QChar('A' + idx % ('Z' - 'A')) + idxStr;
275 idx /= 'Z' - 'A';
276 } while (idx > 'Z' - 'A');
277
278 return idxStr;
279 }
280
281 void
addScenario(Scenario * s)282 Project::addScenario(Scenario* s)
283 {
284 scenarioList.append(s);
285
286 /* This is not too efficient, but since there are usually only a few
287 * scenarios in a project, this doesn't hurt too much. */
288 scenarioList.createIndex(true);
289 scenarioList.createIndex(false);
290 }
291
292 void
deleteScenario(Scenario * s)293 Project::deleteScenario(Scenario* s)
294 {
295 if (scenarioList.contains(s)) {
296 scenarioList.removeAt(scenarioList.indexOf(s));
297 }
298 }
299
300 void
setResourceLimits(UsageLimits * l)301 Project::setResourceLimits(UsageLimits* l)
302 {
303 if (resourceLimits)
304 delete resourceLimits;
305 resourceLimits = l;
306 }
307
308 void
addTask(Task * t)309 Project::addTask(Task* t)
310 {
311 taskList.append(t);
312 }
313
314 void
deleteTask(Task * t)315 Project::deleteTask(Task* t)
316 {
317 if (taskList.contains(t)) {
318 taskList.removeAt(taskList.indexOf(t));
319 }
320 }
321
322 bool
addTaskAttribute(const QString & id,CustomAttributeDefinition * cad)323 Project::addTaskAttribute(const QString& id, CustomAttributeDefinition* cad)
324 {
325 if (taskAttributes.contains(id))
326 return false;
327
328 taskAttributes.insert(id, cad);
329 return true;
330 }
331
332 const CustomAttributeDefinition*
getTaskAttribute(const QString & id) const333 Project::getTaskAttribute(const QString& id) const
334 {
335 return taskAttributes[id];
336 }
337
338 void
addShift(Shift * s)339 Project::addShift(Shift* s)
340 {
341 shiftList.append(s);
342 }
343
344 void
deleteShift(Shift * s)345 Project::deleteShift(Shift* s)
346 {
347 if (shiftList.contains(s)) {
348 shiftList.removeAt(shiftList.indexOf(s));
349 }
350 }
351
352 void
addResource(Resource * r)353 Project::addResource(Resource* r)
354 {
355 qDebug()<<"Project::addResource:"<<r<<resourceList;
356 resourceList.append(r);
357 }
358
359 void
deleteResource(Resource * r)360 Project::deleteResource(Resource* r)
361 {
362 if (resourceList.contains(r)) {
363 resourceList.removeAt(resourceList.indexOf(r));
364 }
365 }
366
367 bool
addResourceAttribute(const QString & id,CustomAttributeDefinition * cad)368 Project::addResourceAttribute(const QString& id,
369 CustomAttributeDefinition* cad)
370 {
371 if (resourceAttributes.contains(id))
372 return false;
373
374 resourceAttributes.insert(id, cad);
375 return true;
376 }
377
378 const CustomAttributeDefinition*
getResourceAttribute(const QString & id) const379 Project::getResourceAttribute(const QString& id) const
380 {
381 return resourceAttributes[id];
382 }
383
384 // void
385 // Project::addAccount(Account* a)
386 // {
387 // accountList.append(a);
388 // }
389 //
390 // void
391 // Project::deleteAccount(Account* a)
392 // {
393 // if (accountList.contains(a)) {
394 // accountList.removeAt(accountList.indexOf(a);
395 // }
396 //
397 // bool
398 // Project::addAccountAttribute(const QString& id,
399 // CustomAttributeDefinition* cad)
400 // {
401 // if (accountAttributes.find(id))
402 // return false;
403 //
404 // accountAttributes.insert(id, cad);
405 // return true;
406 // }
407
408 // const CustomAttributeDefinition*
409 // Project::getAccountAttribute(const QString& id) const
410 // {
411 // return accountAttributes[id];
412 // }
413
414 bool
isWorkingDay(time_t wd) const415 Project::isWorkingDay(time_t wd) const
416 {
417 return !(workingHours[dayOfWeek(wd, false)]->isEmpty() ||
418 isVacation(wd));
419 }
420
421 bool
isWorkingTime(time_t wd) const422 Project::isWorkingTime(time_t wd) const
423 {
424 if (isVacation(wd))
425 return false;
426
427 int dow = dayOfWeek(wd, false);
428 foreach (Interval *i, *getWorkingHours(dow)) {
429 if (i->contains(secondsOfDay(wd)))
430 return true;
431 }
432 return false;
433 }
434
435 bool
isWorkingTime(const Interval & iv) const436 Project::isWorkingTime(const Interval& iv) const
437 {
438 if (isVacation(iv.getStart()))
439 return false;
440
441 int dow = dayOfWeek(iv.getStart(), false);
442 foreach (Interval *i, *(workingHours[dow]))
443 {
444 if (i->contains(Interval(secondsOfDay(iv.getStart()),
445 secondsOfDay(iv.getEnd()))))
446 return true;
447 }
448 return false;
449 }
450
451 int
calcWorkingDays(const Interval & iv) const452 Project::calcWorkingDays(const Interval& iv) const
453 {
454 int workingDays = 0;
455
456 for (time_t s = midnight(iv.getStart()); s <= iv.getEnd();
457 s = sameTimeNextDay(s))
458 if (isWorkingDay(s))
459 workingDays++;
460
461 return workingDays;
462 }
463
464 double
convertToDailyLoad(long secs) const465 Project::convertToDailyLoad(long secs) const
466 {
467 return ((double) secs / (dailyWorkingHours * ONEHOUR));
468 }
469
470 // void
471 // Project::addJournalEntry(JournalEntry* entry)
472 // {
473 // journal.inSort(entry);
474 // }
475
476 // Journal::Iterator
477 // Project::getJournalIterator() const
478 // {
479 // return Journal::Iterator(journal);
480 // }
481
482 bool
pass2(bool noDepCheck)483 Project::pass2(bool noDepCheck)
484 {
485 int oldErrors = TJMH.getErrors();
486
487 if (taskList.isEmpty())
488 {
489 TJMH.errorMessage(xi18nc("@info/plain", "The project does not contain any tasks."));
490 return false;
491 }
492 qDebug()<<"pass2 task info:";
493 foreach (CoreAttributes *a, taskList) {
494 Task *t = static_cast<Task*>(a);
495 qDebug()<<t->getName()<<t->getDuration(0)<<t->getPrecedes()<<t->getDepends();
496 }
497 QMap<QString, Task*> idHash;
498
499 /* The optimum size for the localtime hash is twice the number of time
500 * slots times 2 (because of timeslot and timeslot - 1s). */
501 initUtility(4 * ((end - start) / scheduleGranularity));
502
503 // Generate sequence numbers for all lists.
504 taskList.createIndex(true);
505 resourceList.createIndex(true);
506 // accountList.createIndex(true);
507 shiftList.createIndex(true);
508
509 // Initialize random generator.
510 srand((int) start);
511
512 // Create hash to map task IDs to pointers.
513 foreach (CoreAttributes *t, taskList)
514 {
515 idHash.insert(static_cast<Task*>(t)->getId(), static_cast<Task*>(t));
516 }
517 // Create cross links from dependency lists.
518 foreach (CoreAttributes *t, taskList)
519 static_cast<Task*>(t)->xRef(idHash);
520
521 foreach (CoreAttributes *t, taskList)
522 {
523 // Set dates according to implicit dependencies
524 static_cast<Task*>(t)->implicitXRef();
525
526 // Sort allocations properly
527 static_cast<Task*>(t)->sortAllocations();
528
529 // Save so far booked resources as specified resources
530 static_cast<Task*>(t)->saveSpecifiedBookedResources();
531 }
532
533 // Save a copy of all manually booked resources.
534 foreach (CoreAttributes *r, resourceList)
535 static_cast<Resource*>(r)->saveSpecifiedBookings();
536
537 /* Now we can copy the missing values from the plan scenario to the other
538 * scenarios. */
539 if (scenarioList.count() > 1)
540 {
541 qWarning()<<"TODO";
542 /* for (ScenarioListIterator sli(scenarioList[0]->getSubListIterator());
543 *sli; ++sli)
544 overlayScenario(0, (*sli)->getSequenceNo() - 1);*/
545 }
546
547 // Now check that all tasks have sufficient data to be scheduled.
548 setProgressInfo(QString("Checking scheduling data..."));
549 bool error = false;
550 foreach (CoreAttributes *s, scenarioList) {
551 foreach (CoreAttributes *t, taskList) {
552 if (!static_cast<Task*>(t)->preScheduleOk(static_cast<Scenario*>(s)->getSequenceNo() - 1))
553 {
554 error = true;
555 }
556 }
557 }
558 if (error)
559 return false;
560
561 if (!noDepCheck)
562 {
563 setProgressInfo(QString("Searching for dependency loops ..."));
564 if (DEBUGPS(1))
565 tjDebug("Searching for dependency loops ...");
566 // Check all tasks for dependency loops.
567 LDIList chkedTaskList;
568 foreach (CoreAttributes *t, taskList) {
569 if (static_cast<Task*>(t)->loopDetector(chkedTaskList))
570 return false;
571 }
572
573 setProgressInfo(QString("Searching for underspecified tasks ..."));
574 if (DEBUGPS(1))
575 tjDebug("Searching for underspecified tasks ...");
576 foreach (CoreAttributes *s, scenarioList) {
577 foreach (CoreAttributes *t, taskList) {
578 if (!static_cast<Task*>(t)->checkDetermination(static_cast<Scenario*>(s)->getSequenceNo() - 1))
579 error = true;
580 }
581 }
582
583 if (error)
584 return false;
585 }
586 TJ::TaskList starts;
587 TJ::TaskList ends;
588 QStringList tl;
589 foreach (TJ::CoreAttributes *t, taskList) {
590 tl << t->getName();
591 if (! static_cast<TJ::Task*>(t)->hasPrevious()) {
592 starts << static_cast<TJ::Task*>(t);
593 tl << "(s)";
594 }
595 if (! static_cast<TJ::Task*>(t)->hasFollowers()) {
596 ends << static_cast<TJ::Task*>(t);
597 tl << "(e)";
598 }
599 }
600 tl.clear();
601 foreach (TJ::CoreAttributes *t, taskList) {
602 tl << t->getName();
603 if (! static_cast<TJ::Task*>(t)->hasPrevious()) {
604 starts << static_cast<TJ::Task*>(t);
605 tl << "(s)";
606 }
607 if (! static_cast<TJ::Task*>(t)->hasFollowers()) {
608 ends << static_cast<TJ::Task*>(t);
609 tl << "(e)";
610 }
611 }
612 if (DEBUGPS(2)) {
613 qDebug()<<"Tasks:"<<tl;
614 qDebug()<<"Depends/precedes: -------------------";
615 tl.clear();
616 foreach (TJ::CoreAttributes *t, taskList) {
617 tl << t->getName() + (static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)") + " depends: ";
618 for (QListIterator<TJ::TaskDependency*> it = static_cast<TJ::Task*>(t)->getDependsIterator(); it.hasNext();) {
619 const TJ::Task *a = it.next()->getTaskRef();
620 QString s = a->getName() + (a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)");
621 tl << s;
622 }
623 qDebug()<<tl; tl.clear();
624 tl << t->getName() + (static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)") + " precedes: ";
625 for (QListIterator<TJ::TaskDependency*> it = static_cast<TJ::Task*>(t)->getPrecedesIterator(); it.hasNext();) {
626 const TJ::Task *a = it.next()->getTaskRef();
627 QString s = a->getName() + (a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)");
628 tl << s;
629 }
630 qDebug()<<tl; tl.clear();
631 }
632 qDebug()<<"Followers/previous: -------------------";
633 tl.clear();
634 foreach (TJ::CoreAttributes *t, taskList) {
635 tl << t->getName() + (static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)") + " followers: ";
636 for (TJ::TaskListIterator it = static_cast<TJ::Task*>(t)->getFollowersIterator(); it.hasNext();) {
637 const TJ::Task *a = static_cast<TJ::Task*>(it.next());
638 QString s = a->getName() + (a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)");
639 tl << s;
640 }
641 qDebug()<<tl; tl.clear();
642 tl << t->getName() + (static_cast<TJ::Task*>(t)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)") + " previous: ";
643 for (TJ::TaskListIterator it = static_cast<TJ::Task*>(t)->getPreviousIterator(); it.hasNext();) {
644 const TJ::Task *a = static_cast<TJ::Task*>(it.next());
645 QString s = a->getName() + (a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)");
646 tl << s;
647 }
648 qDebug()<<tl; tl.clear();
649 }
650 qDebug()<<"Successors/predecessors: -------------------";
651 tl.clear();
652 foreach (TJ::CoreAttributes *c, taskList) {
653 tl << c->getName() + (static_cast<TJ::Task*>(c)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)") + " successors: ";
654 foreach (TJ::CoreAttributes *t, static_cast<TJ::Task*>(c)->getSuccessors()) {
655 TJ::Task *a = static_cast<TJ::Task*>(t);
656 QString s = a->getName() + (a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)");
657 tl << s;
658 }
659 qDebug()<<tl; tl.clear();
660 tl << c->getName() + (static_cast<TJ::Task*>(c)->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)") + " predecessors: ";
661 foreach (TJ::CoreAttributes *t, static_cast<TJ::Task*>(c)->getPredecessors()) {
662 TJ::Task *a = static_cast<TJ::Task*>(t);
663 QString s = a->getName() + (a->getScheduling() == TJ::Task::ASAP ? " (ASAP)" : " (ALAP)");
664 tl << s;
665 }
666 qDebug()<<tl; tl.clear();
667 }
668 }
669 return TJMH.getErrors() == oldErrors;
670 }
671
672 bool
scheduleScenario(Scenario * sc)673 Project::scheduleScenario(Scenario* sc)
674 {
675 int oldErrors = TJMH.getErrors();
676
677 // setProgressInfo(QString("Scheduling scenario %1...").arg(sc->getName()));
678
679 int scIdx = sc->getSequenceNo() - 1;
680 prepareScenario(scIdx);
681
682 if (!schedule(scIdx))
683 {
684 if (DEBUGPS(2))
685 tjDebug()<<"Scheduling errors in scenario: "<<(sc->getId());
686 if (breakFlag)
687 return false;
688 }
689 finishScenario(scIdx);
690
691 foreach (CoreAttributes *r, resourceList)
692 {
693 if (!static_cast<Resource*>(r)->bookingsOk(scIdx))
694 break;
695 }
696
697 return TJMH.getErrors() == oldErrors;
698 }
699
700 void
completeBuffersAndIndices()701 Project::completeBuffersAndIndices()
702 {
703 foreach (CoreAttributes *t, taskList) {
704 static_cast<Task*>(t)->computeBuffers();
705 }
706 /* Create indices for all lists according to their default sorting
707 * criteria. */
708 taskList.createIndex();
709 resourceList.createIndex();
710 // accountList.createIndex();
711 shiftList.createIndex();
712 }
713
714 bool
scheduleAllScenarios()715 Project::scheduleAllScenarios()
716 {
717 bool schedulingOk = true;
718 foreach (CoreAttributes *s, scenarioList) {
719 if (static_cast<Scenario*>(s)->getEnabled())
720 {
721 if (DEBUGPS(1))
722 tjDebug()<<"Scheduling scenario:"<<static_cast<Scenario*>(s)->getId();
723
724 if (!scheduleScenario(static_cast<Scenario*>(s)))
725 schedulingOk = false;
726 if (breakFlag)
727 return false;
728 }
729 }
730
731 completeBuffersAndIndices();
732
733 return schedulingOk;
734 }
735
736 void
overlayScenario(int base,int sc)737 Project::overlayScenario(int base, int sc)
738 {
739 foreach (CoreAttributes *t, taskList) {
740 static_cast<Task*>(t)->overlayScenario(base, sc);
741 }
742 foreach (CoreAttributes *s, scenarioList[sc]->getSubList()) {
743 overlayScenario(sc, static_cast<Scenario*>(s)->getSequenceNo() - 1);
744 }
745 }
746
747 void
prepareScenario(int sc)748 Project::prepareScenario(int sc)
749 {
750 foreach (CoreAttributes *r, resourceList) {
751 static_cast<Resource*>(r)->prepareScenario(sc);
752 }
753 foreach (CoreAttributes *t, taskList) {
754 static_cast<Task*>(t)->prepareScenario(sc);
755 }
756
757 /* First we compute the criticalness of the individual task without their
758 * dependency context. */
759 foreach (CoreAttributes *t, taskList) {
760 static_cast<Task*>(t)->computeCriticalness(sc);
761 }
762 /* Then we compute the path criticalness that represents the criticalness
763 * of a task taking their dependency context into account. */
764 foreach (CoreAttributes *t, taskList) {
765 static_cast<Task*>(t)->computePathCriticalness(sc);
766 }
767
768 foreach (CoreAttributes *t, taskList) {
769 static_cast<Task*>(t)->propagateInitialValues(sc);
770 }
771
772 if (DEBUGTS(4))
773 {
774 tjDebug("Allocation probabilities for the resources:");
775 foreach (CoreAttributes *r, resourceList) {
776 qDebug()<<QString("Resource %1: %2%")
777 .arg(static_cast<Resource*>(r)->getName())
778 .arg(static_cast<Resource*>(r)->getAllocationProbability(sc));
779 }
780 tjDebug("Criticalnesses of the tasks with respect to resource "
781 "availability:");
782 foreach (CoreAttributes *t, taskList) {
783 qDebug()<<QString("Task %1: %2 %3").arg(static_cast<Task*>(t)->getName())
784 .arg(static_cast<Task*>(t)->getCriticalness(sc))
785 .arg(static_cast<Task*>(t)->getPathCriticalness(sc));
786 }
787 }
788 }
789
790 void
finishScenario(int sc)791 Project::finishScenario(int sc)
792 {
793 foreach (CoreAttributes *r, resourceList) {
794 static_cast<Resource*>(r)->finishScenario(sc);
795 }
796 foreach (CoreAttributes *t, taskList) {
797 static_cast<Task*>(t)->finishScenario(sc);
798 }
799 #if 0
800 /* We need to have finished the scenario for all tasks before we can
801 * calculate the completion degree. */
802 foreach (CoreAttributes *t, taskList) {
803 static_cast<Task*>(t)->calcCompletionDegree(sc);
804 }
805 #endif
806 /* If the user has not set the minSlackRate to 0 we look for critical
807 * paths. */
808 if (getScenario(sc)->getMinSlackRate() > 0.0)
809 {
810 setProgressInfo(QString("Computing critical paths..."));
811 /* The critical path detector needs to know the end of the last task.
812 * So we have to find this out first. */
813 time_t maxEnd = 0;
814 foreach (CoreAttributes *t, taskList) {
815 if (maxEnd < static_cast<Task*>(t)->getEnd(sc))
816 maxEnd = static_cast<Task*>(t)->getEnd(sc);
817 }
818 foreach (CoreAttributes *t, taskList) {
819 static_cast<Task*>(t)->checkAndMarkCriticalPath
820 (sc, getScenario(sc)->getMinSlackRate(), maxEnd);
821 }
822 }
823 }
824
tasksReadyToBeScheduled(int sc,const TaskList & allLeafTasks)825 TaskList Project::tasksReadyToBeScheduled(int sc, const TaskList& allLeafTasks)
826 {
827 TaskList workItems;
828 foreach (CoreAttributes *t, allLeafTasks) {
829 if (static_cast<Task*>(t)->isReadyForScheduling())
830 workItems.append(static_cast<Task*>(t));
831 }
832 if (workItems.isEmpty()) {
833 foreach (CoreAttributes *t, allLeafTasks) {
834 if (!static_cast<Task*>(t)->isSchedulingDone() && !static_cast<Task*>(t)->isReadyForScheduling()) {
835 TJMH.debugMessage("Not ready to be scheduled", t);
836 }
837 }
838 foreach (CoreAttributes *c, allLeafTasks) {
839 Task *t = static_cast<Task*>(c);
840 if (!t->isSchedulingDone() /*&& !t->isRunaway()*/) {
841 if (t->getScheduling() == Task::ASAP) {
842 time_t es = t->earliestStart(sc);
843 //qDebug()<<"schedule rest: earliest start"<<time2ISO(es)<<time2ISO(time_t(1));
844 if (es > 1) { // NOTE: es is 1 if predecessor is not scheduled!
845 t->propagateStart(sc, es);
846 } else if (t->hasAlapPredecessor()) {
847 time_t le = t->latestEnd(sc);
848 if (le > time_t(0)) {
849 t->setScheduling(Task::ALAP);
850 t->propagateEnd(sc, le);
851 }// else qDebug()<<"schedule rest: no end time"<<t;
852 } //else qDebug()<<"schedule rest: no start time"<<t;
853 } else {
854 time_t le = t->latestEnd(sc);
855 if (le > time_t(0)) {
856 t->propagateEnd(sc, le);
857 } else qDebug()<<"ALAP schedule rest: no end time"<<t;
858 }
859 if (t->isReadyForScheduling()) {
860 workItems.append(t);
861 } else qDebug()<<"Schedule rest: not ready"<<t;
862 }
863 if (workItems.isEmpty()) {
864 TaskList lst;
865 foreach (CoreAttributes *c, allLeafTasks) {
866 Task *t = static_cast<Task*>(c);
867 if (!t->isSchedulingDone()) {
868 lst << t;
869 }
870 }
871 if (lst.isEmpty()) {
872 return workItems; // finished
873 }
874 if (DEBUGPS(5)) {
875 if (!lst.isEmpty()) {
876 qDebug()<<"These tasks are still not ready to be scheduled:"<<allLeafTasks;
877 }
878 }
879 }
880 }
881 }
882 /* if (workItems.isEmpty() && getTask("TJ::StartJob")->getScheduling() == Task::ASAP) {
883 foreach (CoreAttributes *c, allLeafTasks) {
884 }
885 } */
886 if (workItems.isEmpty() && getTask("TJ::StartJob")->getScheduling() == Task::ALAP) {
887 qDebug()<<"tasksReadyToSchedule:"<<"backward, try really hard";
888 foreach (CoreAttributes *c, allLeafTasks) {
889 Task *task = static_cast<Task*>(c);
890 if (!task->isSchedulingDone()) {
891 continue;
892 }
893 qDebug()<<"tasksReadyToSchedule:"<<"scheduled task:"<<task<<time2ISO(task->start)<<time2ISO(task->end);
894 Task *predecessor = 0;
895 long gapLength = 0;
896 long gapDuration = 0;
897 foreach (CoreAttributes *c, task->previous) {
898 Task *t = static_cast<Task*>(c);
899 if (t->isSchedulingDone()) {
900 continue;
901 }
902 // get the dependency/longest gap
903 foreach (TaskDependency *d, t->precedes) {
904 if (d->getTaskRef() == task) {
905 predecessor = t;
906 gapLength = qMax(gapLength, d->getGapLength(sc));
907 gapDuration = qMax(gapDuration, d->getGapDuration(sc));
908 }
909 }
910 }
911 if (predecessor == 0) {
912 continue;
913 }
914 time_t potentialDate = task->start - 1;
915 time_t dateBeforeLengthGap;
916 for (dateBeforeLengthGap = potentialDate; gapLength > 0 && dateBeforeLengthGap >= start; dateBeforeLengthGap -= getScheduleGranularity()) {
917 if (isWorkingTime(dateBeforeLengthGap)) {
918 gapLength -= getScheduleGranularity();
919 }
920 if (dateBeforeLengthGap < potentialDate - gapDuration) {
921 potentialDate = dateBeforeLengthGap;
922 } else {
923 potentialDate -= gapDuration;
924 }
925 }
926 qDebug()<<"tasksReadyToSchedule:"<<"schedule predecessor:"<<predecessor<<time2ISO(potentialDate);
927 predecessor->propagateEnd(sc, potentialDate);
928 workItems << predecessor;
929 break;
930 }
931 }
932 return workItems;
933 }
934
935 bool
schedule(int sc)936 Project::schedule(int sc)
937 {
938 int oldErrors = TJMH.getErrors();
939 int maxProgress = 0;
940
941 // The scheduling function only cares about leaf tasks. Container tasks
942 // are scheduled automatically when all their children are scheduled. So
943 // we create a task list that only contains leaf tasks.
944 TaskList allLeafTasks;
945 foreach (CoreAttributes *t, taskList) {
946 if (!static_cast<Task*>(t)->hasSubs()) {
947 allLeafTasks.append(static_cast<Task*>(t));
948 // TJMH.debugMessage("Leaf task", t);
949 }
950 }
951
952 allLeafTasks.setSorting(CoreAttributesList::PrioDown, 0);
953 allLeafTasks.setSorting(CoreAttributesList::PathCriticalnessDown, 1);
954 allLeafTasks.setSorting(CoreAttributesList::SequenceUp, 2);
955 allLeafTasks.sort();
956 maxProgress = allLeafTasks.count();
957 int sortedTasks = 0;
958 foreach (CoreAttributes *t, allLeafTasks) {
959 if (static_cast<Task*>(t)->isSchedulingDone())
960 sortedTasks++;
961 }
962 /* The workItems list contains all tasks that are ready to be scheduled at
963 * any given iteration. When a tasks has been scheduled completely, this
964 * list needs to be updated again as some tasks may now have become ready
965 * to be scheduled. */
966 TaskList workItems = tasksReadyToBeScheduled(sc, allLeafTasks);
967
968 bool done;
969 /* While the scheduling process progresses, the list contains more and
970 * more scheduled tasks. We use the cleanupTimer to remove those in
971 * certain intervals. As we potentially have already completed tasks in
972 * the list when we start, we initialize the timer with a very large
973 * number so the first round of cleanup is done right after the first
974 * scheduling pass. */
975 breakFlag = false;
976 // bool runAwayFound = false;
977 do
978 {
979 done = true;
980 time_t slot = 0;
981 int priority = 0;
982 double pathCriticalness = 0.0;
983 Task::SchedulingInfo schedulingInfo = Task::ASAP;
984
985 /* The task list is sorted by priority. The priority decreases towards
986 * the end of the list. We iterate through the list and look for a
987 * task that can be scheduled. It the determines the time slot that
988 * will be scheduled during this run for all subsequent tasks as well.
989 */
990 foreach (CoreAttributes *t, workItems) {
991 // TJMH.debugMessage(QString("'%1' schedule for slot: %2, (%3 -%4)").arg(static_cast<Task*>(t)->getName()).arg(time2ISO(slot)).arg(time2ISO(start)).arg(time2ISO(end)));
992 if (slot == 0)
993 {
994 /* No time slot has been set yet. Check if this task can be
995 * scheduled and provides a suggestion. */
996 slot = static_cast<Task*>(t)->nextSlot(scheduleGranularity);
997 // TJMH.debugMessage(QString("'%1' first slot: %2, (%3 -%4)").arg(static_cast<Task*>(t)->getName()).arg(time2ISO(slot)).arg(time2ISO(start)).arg(time2ISO(end)), t);
998 /* If not, try the next task. */
999 if (slot == 0)
1000 continue;
1001 priority = static_cast<Task*>(t)->getPriority();
1002 pathCriticalness = static_cast<Task*>(t)->getPathCriticalness(sc);
1003 schedulingInfo = static_cast<Task*>(t)->getScheduling();
1004
1005 if (DEBUGPS(4))
1006 qDebug()<<QString("Task '%1' (Prio %2, Direction: %3) requests slot %4")
1007 .arg(static_cast<Task*>(t)->getName()).arg(static_cast<Task*>(t)->getPriority())
1008 .arg(static_cast<Task*>(t)->getScheduling())
1009 .arg(time2ISO(slot));
1010 /* If the task wants a time slot outside of the project time
1011 * frame, we flag this task as a runaway and go to the next
1012 * task. */
1013 if (slot < start ||
1014 slot > (end - (time_t) scheduleGranularity + 1))
1015 {
1016 if (!static_cast<Task*>(t)->isRunaway()) {
1017 if (slot < start) {
1018 static_cast<Task*>(t)->warningMessage(i18n("Attempt to schedule task to start before project target time"));
1019 } else {
1020 static_cast<Task*>(t)->warningMessage(i18n("Attempt to schedule task to end after project target time"));
1021 }
1022 static_cast<Task*>(t)->setRunaway();
1023 }
1024 // runAwayFound = true;
1025 slot = 0;
1026 continue;
1027 }
1028 }
1029 done = false;
1030 /* Each task has a scheduling direction (forward or backward)
1031 * depending on it's constrains. The task with the highest
1032 * priority/pathCriticalness determines the time slot and hence the
1033 * scheduling direction. Since tasks that have the other direction
1034 * cannot the scheduled then, we have to stop this run as soon as
1035 * we hit a task that runs in the other direction. If we would not
1036 * do this, tasks with lower priority/pathCriticalness would grab
1037 * resources form tasks with higher priority. */
1038 if (static_cast<Task*>(t)->getScheduling() != schedulingInfo &&
1039 !static_cast<Task*>(t)->isMilestone())
1040 {
1041 if (DEBUGPS(4))
1042 qDebug()<<QString("Changing scheduling direction to %1 due to task '%2'")
1043 .arg(static_cast<Task*>(t)->getScheduling())
1044 .arg(static_cast<Task*>(t)->getName());
1045 break;
1046 }
1047 /* We must avoid that lower priority tasks get resources even
1048 * though there are higher priority tasks that are ready to be
1049 * scheduled but have a non-adjacent last slot. If two tasks have
1050 * the same priority the pathCriticalness is being used. */
1051 if (static_cast<Task*>(t)->getPriority() < priority ||
1052 (static_cast<Task*>(t)->getPriority() == priority &&
1053 static_cast<Task*>(t)->getPathCriticalness(sc) < pathCriticalness))
1054 break;
1055
1056 // Schedule this task for the current time slot.
1057 if (static_cast<Task*>(t)->schedule(sc, slot, scheduleGranularity))
1058 {
1059 workItems = tasksReadyToBeScheduled(sc, allLeafTasks);
1060 int oldSortedTasks = sortedTasks;
1061 sortedTasks = 0;
1062 foreach (CoreAttributes *t, allLeafTasks) {
1063 if (static_cast<Task*>(t)->isSchedulingDone())
1064 sortedTasks++;
1065 }
1066 // Update the progress bar after every 10th completed tasks.
1067 if (oldSortedTasks / 10 != sortedTasks / 10)
1068 {
1069 setProgressBar(100 * ((double)sortedTasks / maxProgress), sortedTasks);
1070 setProgressInfo(QString("Scheduling scenario %1 at %2")
1071 .arg(getScenarioId(sc)).arg(time2tjp(slot)));
1072 }
1073 }
1074 }
1075 } while (!done && !breakFlag);
1076
1077 if (breakFlag)
1078 {
1079 setProgressInfo("");
1080 setProgressBar(0, 0);
1081 TJMH.infoMessage(xi18nc("@info/plain", "Scheduling aborted on user request"));
1082 return false;
1083 }
1084 // if (runAwayFound) {
1085 // foreach (CoreAttributes *t, taskList) {
1086 // if (static_cast<Task*>(t)->isRunaway()) {
1087 // if (static_cast<Task*>(t)->getScheduling() == Task::ASAP) {
1088 // TJMH.errorMessage(xi18nc("@info/plain", "Cannot meet the projects target finish time. Try using a later project end date.", t->getName()), t);
1089 // } else {
1090 // TJMH.errorMessage(xi18nc("@info/plain", "Cannot meet the projects target start time. Try using an earlier project start date.", t->getName()), t);
1091 // }
1092 // }
1093 // }
1094 // }
1095 if (TJMH.getErrors() == oldErrors)
1096 setProgressBar(100, 100);
1097
1098 /* Check that the resulting schedule meets all the requirements that the
1099 * user has specified. */
1100 setProgressInfo(QString("Checking schedule of scenario %1")
1101 .arg(getScenarioId(sc)));
1102 checkSchedule(sc);
1103
1104 return TJMH.getErrors() == oldErrors;
1105 }
1106
1107 void
breakScheduling()1108 Project::breakScheduling()
1109 {
1110 breakFlag = true;
1111 }
1112
1113 bool
checkSchedule(int sc) const1114 Project::checkSchedule(int sc) const
1115 {
1116 int oldErrors = TJMH.getErrors();
1117 foreach (CoreAttributes *t, taskList) {
1118 /* Only check top-level tasks, since they recursively check their sub
1119 * tasks. */
1120 if (static_cast<Task*>(t)->getParent() == 0)
1121 static_cast<Task*>(t)->scheduleOk(sc);
1122 if (maxErrors > 0 && TJMH.getErrors() >= maxErrors)
1123 {
1124 TJMH.errorMessage(xi18nc("@info/plain", "Too many errors. Giving up."));
1125 return false;
1126 }
1127 }
1128
1129 return TJMH.getErrors() == oldErrors;
1130 }
1131
1132 // Report*
1133 // Project::getReport(uint idx) const
1134 // {
1135 // QPtrListIterator<Report> it(reports);
1136 // for (uint i = 0; *it && i < idx; ++it, ++i)
1137 // ;
1138 // return *it;
1139 // }
1140 //
1141 // /*QPtr*/ListIterator<Report>
1142 // Project::getReportListIterator() const
1143 // {
1144 // return QPtrListIterator<Report>(reports);
1145 // }
1146 //
1147 // bool
1148 // Project::generateReports() const
1149 // {
1150 // // Generate reports
1151 // int errors = 0;
1152 // for (QPtrListIterator<Report> ri(reports); *ri != 0; ++ri)
1153 // {
1154 // // We generate all but Qt*Reports. Those are for the GUI version.
1155 // if (strncmp((*ri)->getType(), "Qt", 2) != 0)
1156 // {
1157 // if (DEBUGPS(1))
1158 // tjDebug(QString("Generating report '%1' ...")
1159 // .arg((*ri)->getFileName()));
1160 //
1161 // if (!(*ri)->generate())
1162 // errors++;
1163 // }
1164 // }
1165 //
1166 // generateXMLReport();
1167 //
1168 // return errors == 0;
1169 // }
1170 //
1171 // bool Project::generateXMLReport() const
1172 // {
1173 // if (xmlreport)
1174 // return xmlreport->generate();
1175 // else
1176 // return false;
1177 // }
1178
1179 } // namespace TJ
1180