Skip to content

Commit 1104c88

Browse files
committed
Correctly sort Primavera schedules
1 parent 50e8979 commit 1104c88

File tree

8 files changed

+184
-131
lines changed

8 files changed

+184
-131
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
* Improve handling of calendar exceptions in MPX files.
55
* Improve handling of MPP files with large numbers of null tasks.
66
* Improve robustness when reading timephased data.
7+
* Correctly sort Primavera schedules containing WBS entries with no child activities.
78

89
## 7.4.3 (25/05/2018)
910
* Add support for reading the resource "generic" attribute from MPP files.

maven/src/changes/changes.xml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
<action dev="joniles" type="update">Improve handling of calendar exceptions in MPX files.</action>
1010
<action dev="joniles" type="update">Improve handling of MPP files with large numbers of null tasks.</action>
1111
<action dev="joniles" type="update">Improve robustness when reading timephased data.</action>
12+
<action dev="joniles" type="update">Correctly sort Primavera schedules containing WBS entries with no child activities.</action>
1213
</release>
1314
<release date="25/05/2018" version="7.4.3">
1415
<action dev="joniles" type="add">Add support for reading the resource "generic" attribute from MPP files.</action>
Lines changed: 114 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,114 @@
1+
/*
2+
* file: ActivitySorter.java
3+
* author: Jon Iles
4+
* copyright: (c) Packwood Software 2018
5+
* date: 06/06/2018
6+
*/
7+
8+
/*
9+
* This library is free software; you can redistribute it and/or modify it
10+
* under the terms of the GNU Lesser General Public License as published by the
11+
* Free Software Foundation; either version 2.1 of the License, or (at your
12+
* option) any later version.
13+
*
14+
* This library is distributed in the hope that it will be useful, but
15+
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY
16+
* or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public
17+
* License for more details.
18+
*
19+
* You should have received a copy of the GNU Lesser General Public License
20+
* along with this library; if not, write to the Free Software Foundation, Inc.,
21+
* 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA.
22+
*/
23+
24+
package net.sf.mpxj.primavera;
25+
26+
import java.util.Collections;
27+
import java.util.Comparator;
28+
import java.util.List;
29+
import java.util.Set;
30+
31+
import net.sf.mpxj.ChildTaskContainer;
32+
import net.sf.mpxj.FieldType;
33+
import net.sf.mpxj.Task;
34+
35+
/**
36+
* Ensures correct activity order within.
37+
*/
38+
class ActivitySorter
39+
{
40+
/**
41+
* Constructor.
42+
*
43+
* @param activityIDField field containing the Activity ID attribute
44+
* @param wbsTasks set of WBS tasks
45+
*/
46+
public ActivitySorter(FieldType activityIDField, Set<Task> wbsTasks)
47+
{
48+
m_activityIDField = activityIDField;
49+
m_wbsTasks = wbsTasks;
50+
}
51+
52+
/**
53+
* Recursively sort the supplied child tasks.
54+
*
55+
* @param container child tasks
56+
*/
57+
public void sort(ChildTaskContainer container)
58+
{
59+
// Do we have any tasks?
60+
List<Task> tasks = container.getChildTasks();
61+
if (!tasks.isEmpty())
62+
{
63+
for (Task task : tasks)
64+
{
65+
//
66+
// Sort child activities
67+
//
68+
sort(task);
69+
70+
//
71+
// Sort Order:
72+
// 1. Activities come first
73+
// 2. WBS come last
74+
// 3. Activities ordered by activity ID
75+
// 4. WBS ordered by ID
76+
//
77+
Collections.sort(tasks, new Comparator<Task>()
78+
{
79+
@Override public int compare(Task t1, Task t2)
80+
{
81+
boolean t1IsWbs = m_wbsTasks.contains(t1);
82+
boolean t2IsWbs = m_wbsTasks.contains(t2);
83+
84+
// Both are WBS
85+
if (t1IsWbs && t2IsWbs)
86+
{
87+
return t1.getID().compareTo(t2.getID());
88+
}
89+
90+
// Both are activities
91+
if (!t1IsWbs && !t2IsWbs)
92+
{
93+
String activityID1 = (String) t1.getCurrentValue(m_activityIDField);
94+
String activityID2 = (String) t2.getCurrentValue(m_activityIDField);
95+
96+
if (activityID1 == null || activityID2 == null)
97+
{
98+
return (activityID1 == null && activityID2 == null ? 0 : (activityID1 == null ? 1 : -1));
99+
}
100+
101+
return activityID1.compareTo(activityID2);
102+
}
103+
104+
// One activity one WBS
105+
return t1IsWbs ? 1 : -1;
106+
}
107+
});
108+
}
109+
}
110+
}
111+
112+
final FieldType m_activityIDField;
113+
final Set<Task> m_wbsTasks;
114+
}

src/net/sf/mpxj/primavera/PrimaveraPMFileReader.java

Lines changed: 8 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@
2525

2626
import java.io.InputStream;
2727
import java.util.Collections;
28-
import java.util.Comparator;
2928
import java.util.Date;
3029
import java.util.HashMap;
3130
import java.util.HashSet;
@@ -47,7 +46,6 @@
4746
import org.xml.sax.XMLReader;
4847

4948
import net.sf.mpxj.AssignmentField;
50-
import net.sf.mpxj.ChildTaskContainer;
5149
import net.sf.mpxj.ConstraintType;
5250
import net.sf.mpxj.CustomFieldContainer;
5351
import net.sf.mpxj.DateRange;
@@ -421,17 +419,20 @@ private void processTasks(ProjectType project)
421419
{
422420
List<WBSType> wbs = project.getWBS();
423421
List<ActivityType> tasks = project.getActivity();
424-
425422
Set<Integer> uniqueIDs = new HashSet<Integer>();
423+
Set<Task> wbsTasks = new HashSet<Task>();
426424

427425
//
428426
// Read WBS entries and create tasks
429427
//
428+
Collections.sort(wbs, WBS_ROW_COMPARATOR);
429+
430430
for (WBSType row : wbs)
431431
{
432432
Task task = m_projectFile.addTask();
433433
Integer uniqueID = row.getObjectId();
434434
uniqueIDs.add(uniqueID);
435+
wbsTasks.add(task);
435436

436437
task.setUniqueID(uniqueID);
437438
task.setName(row.getName());
@@ -592,72 +593,12 @@ private void processTasks(ProjectType project)
592593
m_eventManager.fireTaskReadEvent(task);
593594
}
594595

595-
sortActivities(TaskField.TEXT1, m_projectFile);
596+
new ActivitySorter(TaskField.TEXT1, wbsTasks).sort(m_projectFile);
597+
596598
updateStructure();
597599
updateDates();
598600
}
599601

600-
/**
601-
* Ensure activities are sorted into Activity ID order to match Primavera.
602-
*
603-
* @param activityIDField field containing the Activity ID value
604-
* @param container object containing the tasks to process
605-
*/
606-
private void sortActivities(final FieldType activityIDField, ChildTaskContainer container)
607-
{
608-
// Do we have any tasks?
609-
List<Task> tasks = container.getChildTasks();
610-
if (!tasks.isEmpty())
611-
{
612-
for (Task task : tasks)
613-
{
614-
//
615-
// Sort child activities
616-
//
617-
sortActivities(activityIDField, task);
618-
619-
//
620-
// Sort Order:
621-
// 1. Activities come first
622-
// 2. WBS come last
623-
// 3. Activities ordered by activity ID
624-
// 4. WBS ordered by ID
625-
//
626-
Collections.sort(tasks, new Comparator<Task>()
627-
{
628-
@Override public int compare(Task t1, Task t2)
629-
{
630-
boolean t1HasChildren = !t1.getChildTasks().isEmpty();
631-
boolean t2HasChildren = !t2.getChildTasks().isEmpty();
632-
633-
// Both are WBS
634-
if (t1HasChildren && t2HasChildren)
635-
{
636-
return t1.getID().compareTo(t2.getID());
637-
}
638-
639-
// Both are activities
640-
if (!t1HasChildren && !t2HasChildren)
641-
{
642-
String activityID1 = (String) t1.getCurrentValue(activityIDField);
643-
String activityID2 = (String) t2.getCurrentValue(activityIDField);
644-
645-
if (activityID1 == null || activityID2 == null)
646-
{
647-
return (activityID1 == null && activityID2 == null ? 0 : (activityID1 == null ? 1 : -1));
648-
}
649-
650-
return activityID1.compareTo(activityID2);
651-
}
652-
653-
// One activity one WBS
654-
return t1HasChildren ? 1 : -1;
655-
}
656-
});
657-
}
658-
}
659-
}
660-
661602
/**
662603
* The Primavera WBS entries we read in as tasks have user-entered start and end dates
663604
* which aren't calculated or adjusted based on the child task dates. We try
@@ -1091,4 +1032,6 @@ private Integer mapTaskID(Integer id)
10911032
MILESTONE_MAP.put("Finish Milestone", Boolean.TRUE);
10921033
MILESTONE_MAP.put("WBS Summary", Boolean.FALSE);
10931034
}
1035+
1036+
private static final WbsRowComparatorPMXML WBS_ROW_COMPARATOR = new WbsRowComparatorPMXML();
10941037
}

src/net/sf/mpxj/primavera/PrimaveraReader.java

Lines changed: 4 additions & 63 deletions
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,6 @@
4141
import net.sf.mpxj.AssignmentField;
4242
import net.sf.mpxj.Availability;
4343
import net.sf.mpxj.AvailabilityTable;
44-
import net.sf.mpxj.ChildTaskContainer;
4544
import net.sf.mpxj.ConstraintType;
4645
import net.sf.mpxj.CostRateTable;
4746
import net.sf.mpxj.CostRateTableEntry;
@@ -578,6 +577,7 @@ public void processTasks(List<Row> wbs, List<Row> tasks, List<Row> udfVals)
578577
ProjectProperties projectProperties = m_project.getProjectProperties();
579578
String projectName = projectProperties.getName();
580579
Set<Integer> uniqueIDs = new HashSet<Integer>();
580+
Set<Task> wbsTasks = new HashSet<Task>();
581581

582582
//
583583
// We set the project name when we read the project properties, but that's just
@@ -602,6 +602,7 @@ public void processTasks(List<Row> wbs, List<Row> tasks, List<Row> udfVals)
602602
task.setProject(projectName); // P6 task always belongs to project
603603
processFields(m_wbsFields, row, task);
604604
uniqueIDs.add(task.getUniqueID());
605+
wbsTasks.add(task);
605606
m_eventManager.fireTaskReadEvent(task);
606607
}
607608

@@ -700,7 +701,8 @@ public void processTasks(List<Row> wbs, List<Row> tasks, List<Row> udfVals)
700701
m_eventManager.fireTaskReadEvent(task);
701702
}
702703

703-
sortActivities(activityIDField, m_project);
704+
new ActivitySorter(TaskField.TEXT1, wbsTasks).sort(m_project);
705+
704706
updateStructure();
705707
updateDates();
706708
updateWork();
@@ -940,67 +942,6 @@ private void populateField(FieldContainer container, FieldType target, FieldType
940942
container.set(target, value);
941943
}
942944

943-
/**
944-
* Ensure activities are sorted into Activity ID order to match Primavera.
945-
*
946-
* @param activityIDField field containing the Activity ID value
947-
* @param container object containing the tasks to process
948-
*/
949-
private void sortActivities(final FieldType activityIDField, ChildTaskContainer container)
950-
{
951-
// Do we have any tasks?
952-
List<Task> tasks = container.getChildTasks();
953-
if (!tasks.isEmpty())
954-
{
955-
for (Task task : tasks)
956-
{
957-
//
958-
// Sort child activities
959-
//
960-
sortActivities(activityIDField, task);
961-
962-
//
963-
// Sort Order:
964-
// 1. Activities come first
965-
// 2. WBS come last
966-
// 3. Activities ordered by activity ID
967-
// 4. WBS ordered by ID
968-
//
969-
Collections.sort(tasks, new Comparator<Task>()
970-
{
971-
@Override public int compare(Task t1, Task t2)
972-
{
973-
boolean t1HasChildren = !t1.getChildTasks().isEmpty();
974-
boolean t2HasChildren = !t2.getChildTasks().isEmpty();
975-
976-
// Both are WBS
977-
if (t1HasChildren && t2HasChildren)
978-
{
979-
return t1.getID().compareTo(t2.getID());
980-
}
981-
982-
// Both are activities
983-
if (!t1HasChildren && !t2HasChildren)
984-
{
985-
String activityID1 = (String) t1.getCurrentValue(activityIDField);
986-
String activityID2 = (String) t2.getCurrentValue(activityIDField);
987-
988-
if (activityID1 == null || activityID2 == null)
989-
{
990-
return (activityID1 == null && activityID2 == null ? 0 : (activityID1 == null ? 1 : -1));
991-
}
992-
993-
return activityID1.compareTo(activityID2);
994-
}
995-
996-
// One activity one WBS
997-
return t1HasChildren ? 1 : -1;
998-
}
999-
});
1000-
}
1001-
}
1002-
}
1003-
1004945
/**
1005946
* Iterates through the tasks setting the correct
1006947
* outline level and ID values.

src/net/sf/mpxj/primavera/PrimaveraXERFileReader.java

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1027,5 +1027,5 @@ private enum XerFieldType
10271027
REQUIRED_TABLES.add("schedoptions");
10281028
}
10291029

1030-
private static final WbsRowComparator WBS_ROW_COMPARATOR = new WbsRowComparator();
1030+
private static final WbsRowComparatorXER WBS_ROW_COMPARATOR = new WbsRowComparatorXER();
10311031
}

0 commit comments

Comments
 (0)