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