/* * file: PrimaveraReader.java * author: Jon Iles * copyright: (c) Packwood Software 2010 * date: 22/03/2010 */ /* * This library is free software; you can redistribute it and/or modify it * under the terms of the GNU Lesser General Public License as published by the * Free Software Foundation; either version 2.1 of the License, or (at your * option) any later version. * * This library is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY * or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public * License for more details. * * You should have received a copy of the GNU Lesser General Public License * along with this library; if not, write to the Free Software Foundation, Inc., * 59 Temple Place, Suite 330, Boston, MA 02111-1307, USA. */ package net.sf.mpxj.primavera; import java.text.DateFormat; import java.text.ParseException; import java.text.SimpleDateFormat; import java.util.Date; import java.util.HashMap; import java.util.HashSet; import java.util.LinkedHashMap; import java.util.LinkedList; import java.util.List; import java.util.Map; import java.util.Set; import net.sf.mpxj.AssignmentField; import net.sf.mpxj.ConstraintType; import net.sf.mpxj.CurrencySymbolPosition; import net.sf.mpxj.CustomFieldContainer; import net.sf.mpxj.DataType; import net.sf.mpxj.DateRange; import net.sf.mpxj.Day; import net.sf.mpxj.DayType; import net.sf.mpxj.Duration; import net.sf.mpxj.EventManager; import net.sf.mpxj.FieldContainer; import net.sf.mpxj.FieldType; import net.sf.mpxj.FieldTypeClass; import net.sf.mpxj.Priority; import net.sf.mpxj.ProjectCalendar; import net.sf.mpxj.ProjectCalendarHours; import net.sf.mpxj.ProjectConfig; import net.sf.mpxj.ProjectFile; import net.sf.mpxj.ProjectProperties; import net.sf.mpxj.Relation; import net.sf.mpxj.RelationType; import net.sf.mpxj.Resource; import net.sf.mpxj.ResourceAssignment; import net.sf.mpxj.ResourceField; import net.sf.mpxj.ResourceType; import net.sf.mpxj.Task; import net.sf.mpxj.TaskField; import net.sf.mpxj.TaskType; import net.sf.mpxj.common.BooleanHelper; import net.sf.mpxj.common.DateHelper; import net.sf.mpxj.common.NumberHelper; /** * This class provides a generic front end to read project data from * a database. */ final class PrimaveraReader { /** * Constructor. * * @param udfCounters user defined field data types * @param resourceFields resource field mapping * @param wbsFields wbs field mapping * @param taskFields task field mapping * @param assignmentFields assignment field mapping * @param aliases alias mapping * @param matchPrimaveraWBS determine WBS behaviour */ public PrimaveraReader(UserFieldCounters udfCounters, Map<FieldType, String> resourceFields, Map<FieldType, String> wbsFields, Map<FieldType, String> taskFields, Map<FieldType, String> assignmentFields, Map<FieldType, String> aliases, boolean matchPrimaveraWBS) { m_project = new ProjectFile(); m_eventManager = m_project.getEventManager(); ProjectConfig config = m_project.getProjectConfig(); config.setAutoTaskUniqueID(false); config.setAutoResourceUniqueID(false); config.setAutoCalendarUniqueID(true); config.setAutoAssignmentUniqueID(false); config.setAutoWBS(false); m_resourceFields = resourceFields; m_wbsFields = wbsFields; m_taskFields = taskFields; m_assignmentFields = assignmentFields; applyAliases(aliases); m_udfCounters = udfCounters; m_udfCounters.reset(); m_matchPrimaveraWBS = matchPrimaveraWBS; } /** * Retrieves the project data read from this file. * * @return project data */ public ProjectFile getProject() { return m_project; } /** * Process project properties. * * @param rows project properties data. */ public void processProjectProperties(List<Row> rows) { if (rows.isEmpty() == false) { Row row = rows.get(0); ProjectProperties properties = m_project.getProjectProperties(); properties.setCreationDate(row.getDate("create_date")); properties.setFinishDate(row.getDate("plan_end_date")); properties.setName(row.getString("proj_short_name")); properties.setStartDate(row.getDate("plan_start_date")); // data_date? properties.setProjectTitle(row.getString("proj_short_name")); properties.setDefaultTaskType(TASK_TYPE_MAP.get(row.getString("def_duration_type"))); properties.setStatusDate(row.getDate("last_recalc_date")); } } /** * Process User Defined Fields (UDF). * * @param userDefinedFields UDFs rows */ public void processUserDefinedFields(List<Row> userDefinedFields) { for (Row row : userDefinedFields) { if ("TASK".equals(row.getString("table_name"))) { parseTaskUDF(row); } } } /** * Process project calendars. * * @param rows project calendar data */ public void processCalendars(List<Row> rows) { for (Row row : rows) { processCalendar(row); } } /** * Process data for an individual calendar. * * @param row calendar data */ public void processCalendar(Row row) { ProjectCalendar calendar = m_project.addCalendar(); Integer id = row.getInteger("clndr_id"); m_calMap.put(id, calendar); calendar.setName(row.getString("clndr_name")); // Process data String calendarData = row.getString("clndr_data"); if (calendarData != null && !calendarData.isEmpty()) { Record root = getCalendarDataRecord(calendarData); if (root != null) { processCalendarDays(calendar, root); processCalendarExceptions(calendar, root); } } m_eventManager.fireCalendarReadEvent(calendar); } /** * Create a structured calendar Record instance from the flat calendar data. * * @param calendarData flat calendar data * @return calendar Record instance */ private Record getCalendarDataRecord(String calendarData) { Record root; try { root = new Record(calendarData); } // // I've come across invalid calendar data in an otherwise fine Primavera // database belonging to a customer. We deal with this gracefully here // rather than propagating an exception. // catch (Exception ex) { root = null; } return root; } /** * Process calendar days of the week. * * @param calendar project calendar * @param root calendar data */ private void processCalendarDays(ProjectCalendar calendar, Record root) { // Retrieve working hours ... Record daysOfWeek = root.getChild("DaysOfWeek"); if (daysOfWeek != null) { for (Record dayRecord : daysOfWeek.getChildren()) { processCalendarHours(calendar, dayRecord); } } } /** * Process hours in a working day. * * @param calendar project calendar * @param dayRecord working day data */ private void processCalendarHours(ProjectCalendar calendar, Record dayRecord) { // ... for each day of the week Day day = Day.getInstance(Integer.parseInt(dayRecord.getField())); // Get hours List<Record> recHours = dayRecord.getChildren(); if (recHours.size() == 0) { // No data -> not working calendar.setWorkingDay(day, false); } else { calendar.setWorkingDay(day, true); // Read hours ProjectCalendarHours hours = calendar.addCalendarHours(day); for (Record recWorkingHours : recHours) { if (recWorkingHours.getValue() != null) { String[] wh = recWorkingHours.getValue().split("\\|"); try { Date start = m_calendarTimeFormat.parse(wh[1]); Date end = m_calendarTimeFormat.parse(wh[3]); hours.addRange(new DateRange(start, end)); } catch (ParseException e) { // silently ignore date parse exceptions } } } } } /** * Process calendar exceptions. * * @param calendar project calendar * @param root calendar data */ private void processCalendarExceptions(ProjectCalendar calendar, Record root) { // Retrieve exceptions Record exceptions = root.getChild("Exceptions"); if (exceptions != null) { for (Record exception : exceptions.getChildren()) { int daysFrom1900 = Integer.parseInt(exception.getValue().split("\\|")[1]); int daysFrom1970 = daysFrom1900 - 25567 - 2; // 25567 -> Number of days between 1900 and 1970. // During tests a 2 days offset was necessary to obtain good dates // However I didn't figured out why there is such a difference. Date startEx = new Date(daysFrom1970 * 24l * 60l * 60l * 1000); calendar.addCalendarException(startEx, startEx); } } } /** * Process resources. * * @param rows resource data */ public void processResources(List<Row> rows) { for (Row row : rows) { Resource resource = m_project.addResource(); processFields(m_resourceFields, row, resource); resource.setResourceCalendar(getResourceCalendar(row.getInteger("clndr_id"))); m_eventManager.fireResourceReadEvent(resource); } } /** * Retrieve the correct calendar for a resource. * * @param calendarID calendar ID * @return calendar for resource */ private ProjectCalendar getResourceCalendar(Integer calendarID) { ProjectCalendar result = null; if (calendarID != null) { ProjectCalendar calendar = m_calMap.get(calendarID); if (calendar != null) { // // If the resource is linked to a base calendar, derive // a default calendar from the base calendar. // if (!calendar.isDerived()) { ProjectCalendar resourceCalendar = m_project.addCalendar(); resourceCalendar.setParent(calendar); resourceCalendar.setWorkingDay(Day.MONDAY, DayType.DEFAULT); resourceCalendar.setWorkingDay(Day.TUESDAY, DayType.DEFAULT); resourceCalendar.setWorkingDay(Day.WEDNESDAY, DayType.DEFAULT); resourceCalendar.setWorkingDay(Day.THURSDAY, DayType.DEFAULT); resourceCalendar.setWorkingDay(Day.FRIDAY, DayType.DEFAULT); resourceCalendar.setWorkingDay(Day.SATURDAY, DayType.DEFAULT); resourceCalendar.setWorkingDay(Day.SUNDAY, DayType.DEFAULT); result = resourceCalendar; } else { // // Primavera seems to allow a calendar to be shared between resources // whereas in the MS Project model there is a one-to-one // relationship. If we find a shared calendar, take a copy of it // if (calendar.getResource() == null) { result = calendar; } else { ProjectCalendar copy = m_project.addCalendar(); copy.copy(calendar); result = copy; } } } } return result; } /** * Process tasks. * * @param wbs WBS task data * @param tasks task data * @param costs task costs */ public void processTasks(List<Row> wbs, List<Row> tasks, List<Row> costs) { processTasks(wbs, tasks, costs, null); } /** * Process tasks. * * @param wbs WBS task data * @param tasks task data * @param costs task costs * @param udfVals User Defined Fields values data */ public void processTasks(List<Row> wbs, List<Row> tasks, List<Row> costs, List<Row> udfVals) { Set<Integer> uniqueIDs = new HashSet<Integer>(); Map<Integer, TaskCosts> taskCostsMap = processCosts(costs); // // Read WBS entries and create tasks // for (Row row : wbs) { Task task = m_project.addTask(); processFields(m_wbsFields, row, task); uniqueIDs.add(task.getUniqueID()); m_eventManager.fireTaskReadEvent(task); } // // Create hierarchical structure // FieldType activityIDField = getActivityIDField(m_wbsFields); m_project.getChildTasks().clear(); for (Row row : wbs) { Task task = m_project.getTaskByUniqueID(row.getInteger("wbs_id")); Task parentTask = m_project.getTaskByUniqueID(row.getInteger("parent_wbs_id")); if (parentTask == null) { m_project.getChildTasks().add(task); } else { m_project.getChildTasks().remove(task); parentTask.getChildTasks().add(task); task.setWBS(parentTask.getWBS() + "." + task.getWBS()); if (activityIDField != null) { task.set(activityIDField, task.getWBS() + " " + task.getName()); } } } // // Read Task entries and create tasks // int nextID = 1; m_clashMap.clear(); for (Row row : tasks) { Task task; Integer parentTaskID = row.getInteger("wbs_id"); Task parentTask = m_project.getTaskByUniqueID(parentTaskID); if (parentTask == null) { task = m_project.addTask(); } else { task = parentTask.addTask(); } processFields(m_taskFields, row, task); task.setMilestone(BooleanHelper.getBoolean(MILESTONE_MAP.get(row.getString("task_type")))); task.setPercentageComplete(calculatePercentComplete(row)); if (m_matchPrimaveraWBS && parentTask != null) { task.setWBS(parentTask.getWBS()); } Integer uniqueID = task.getUniqueID(); if (uniqueIDs.contains(uniqueID)) { while (uniqueIDs.contains(Integer.valueOf(nextID))) { ++nextID; } Integer newUniqueID = Integer.valueOf(nextID); m_clashMap.put(uniqueID, newUniqueID); uniqueID = newUniqueID; task.setUniqueID(uniqueID); } uniqueIDs.add(uniqueID); // // Apply costs if we have any // TaskCosts taskCosts = taskCostsMap.get(row.getInteger("task_id")); if (taskCosts != null) { task.setActualCost(taskCosts.getActual()); task.setCost(taskCosts.getPlanned()); task.setRemainingCost(taskCosts.getRemaining()); } Integer calId = row.getInteger("clndr_id"); ProjectCalendar cal = m_calMap.get(calId); task.setCalendar(cal); Date startDate = row.getDate("act_start_date") == null ? row.getDate("restart_date") : row.getDate("act_start_date"); task.setStart(startDate); Date endDate = row.getDate("act_end_date") == null ? row.getDate("reend_date") : row.getDate("act_end_date"); task.setFinish(endDate); populateField(task, TaskField.WORK, TaskField.BASELINE_WORK, TaskField.ACTUAL_WORK); // Add User Defined Fields List<Row> taskUDF = getTaskUDF(uniqueID, udfVals); for (Row r : taskUDF) { addTaskUDFValue(task, r); } m_eventManager.fireTaskReadEvent(task); } updateStructure(); updateDates(); } /** * Determine which field the Activity ID has been mapped to. * * @param map field map * @return field */ private FieldType getActivityIDField(Map<FieldType, String> map) { FieldType result = null; for (Map.Entry<FieldType, String> entry : map.entrySet()) { if (entry.getValue().equals("task_code")) { result = entry.getKey(); break; } } return result; } /** * Summarise cost values for each task. * * @param costs list of cost rows * @return map of task IDs to costs */ private Map<Integer, TaskCosts> processCosts(List<Row> costs) { Map<Integer, TaskCosts> map = new HashMap<Integer, TaskCosts>(); for (Row cost : costs) { Integer taskID = cost.getInteger("task_id"); TaskCosts taskCosts = map.get(taskID); if (taskCosts == null) { taskCosts = new TaskCosts(); map.put(taskID, taskCosts); } taskCosts.addActual(cost.getDouble("act_cost")); taskCosts.addPlanned(cost.getDouble("target_cost")); taskCosts.addRemaining(cost.getDouble("remain_cost")); } return map; } /** * Configure a new user defined field. * * @param type field type * @param name field name */ private void addUserDefinedField(UserFieldDataType type, String name) { try { TaskField taskField; do { String fieldName = m_udfCounters.nextName(type); taskField = TaskField.valueOf(fieldName); } while (m_taskFields.containsKey(taskField) || m_wbsFields.containsKey(taskField)); m_project.getCustomFields().getCustomField(taskField).setAlias(name); } catch (Exception ex) { // // SF#227: If we get an exception thrown here... it's likely that // we've run out of user defined fields, for example // there are only 30 TEXT fields. We'll ignore this: the user // defined field won't be mapped to an alias, so we'll // ignore it when we read in the values. // } } /** * Parse a user defined field for a task. * * @param row UDF data */ private void parseTaskUDF(Row row) { Integer fieldId = Integer.valueOf(row.getString("udf_type_id")); String fieldType = row.getString("logical_data_type"); String fieldName = row.getString("udf_type_label"); m_udfMap.put(fieldId, fieldName); addUserDefinedField(UserFieldDataType.valueOf(fieldType), fieldName); } /** * Adds a user defined field value to a task. * * @param task Task instance * @param row UDF data */ private void addTaskUDFValue(Task task, Row row) { Integer fieldId = Integer.valueOf(row.getString("udf_type_id")); String fieldName = m_udfMap.get(fieldId); Object value = null; FieldType field = m_project.getCustomFields().getFieldByAlias(FieldTypeClass.TASK, fieldName); if (field != null) { DataType fieldType = field.getDataType(); switch (fieldType) { case DATE: { value = row.getDate("udf_date"); break; } case CURRENCY: case NUMERIC: { value = row.getDouble("udf_number"); break; } case GUID: case INTEGER: { value = row.getInteger("udf_code_id"); break; } default: { value = row.getString("udf_text"); break; } } task.set(field, value); } } /** * Retrieve the user defined values for a given task. * * @param taskID target task ID * @param udfs user defined fields * @return user defined fields for the target task */ private List<Row> getTaskUDF(Integer taskID, List<Row> udfs) { List<Row> udf = new LinkedList<Row>(); if (udfs != null) { for (Row row : udfs) { if (taskID.equals(row.getInteger("fk_id"))) { udf.add(row); } } } return udf; } /* private String getNotes(List<Row> notes, String keyField, int keyValue, String notesField) { String result = null; for (Row row : notes) { if (row.getInt(keyField) == keyValue) { result = row.getString(notesField); break; } } return result; } */ /** * Populates a field based on baseline and actual values. * * @param container field container * @param target target field * @param baseline baseline field * @param actual actual field */ private void populateField(FieldContainer container, FieldType target, FieldType baseline, FieldType actual) { Object value = container.getCachedValue(actual); if (value == null) { value = container.getCachedValue(baseline); } container.set(target, value); } /** * Iterates through the tasks setting the correct * outline level and ID values. */ private void updateStructure() { int id = 1; Integer outlineLevel = Integer.valueOf(1); for (Task task : m_project.getChildTasks()) { id = updateStructure(id, task, outlineLevel); } } /** * Iterates through the tasks setting the correct * outline level and ID values. * * @param id current ID value * @param task current task * @param outlineLevel current outline level * @return next ID value */ private int updateStructure(int id, Task task, Integer outlineLevel) { task.setID(Integer.valueOf(id++)); task.setOutlineLevel(outlineLevel); outlineLevel = Integer.valueOf(outlineLevel.intValue() + 1); for (Task childTask : task.getChildTasks()) { id = updateStructure(id, childTask, outlineLevel); } return id; } /** * The Primavera WBS entries we read in as tasks have user-entered start and end dates * which aren't calculated or adjusted based on the child task dates. We try * to compensate for this by using these user-entered dates as baseline dates, and * deriving the planned start, actual start, planned finish and actual finish from * the child tasks. This method recursively descends through the tasks to do this. */ private void updateDates() { for (Task task : m_project.getChildTasks()) { updateDates(task); } } /** * See the notes above. * * @param parentTask parent task. */ private void updateDates(Task parentTask) { int finished = 0; Date plannedStartDate = parentTask.getStart(); Date plannedFinishDate = parentTask.getFinish(); Date actualStartDate = parentTask.getActualStart(); Date actualFinishDate = parentTask.getActualFinish(); for (Task task : parentTask.getChildTasks()) { updateDates(task); if (plannedStartDate == null || DateHelper.compare(plannedStartDate, task.getStart()) > 0) { plannedStartDate = task.getStart(); } if (actualStartDate == null || DateHelper.compare(actualStartDate, task.getActualStart()) > 0) { actualStartDate = task.getActualStart(); } if (plannedFinishDate == null || DateHelper.compare(plannedFinishDate, task.getFinish()) < 0) { plannedFinishDate = task.getFinish(); } if (actualFinishDate == null || DateHelper.compare(actualFinishDate, task.getActualFinish()) < 0) { actualFinishDate = task.getFinish(); } if (task.getActualFinish() != null) { ++finished; } } parentTask.setStart(plannedStartDate); parentTask.setFinish(plannedFinishDate); parentTask.setActualStart(actualStartDate); // // Only if all child tasks have actual finish dates do we // set the actual finish date on the parent task. // if (finished == parentTask.getChildTasks().size()) { parentTask.setActualFinish(actualFinishDate); } } /** * Processes predecessor data. * * @param rows predecessor data */ public void processPredecessors(List<Row> rows) { for (Row row : rows) { Task currentTask = m_project.getTaskByUniqueID(mapTaskID(row.getInteger("task_id"))); Task predecessorTask = m_project.getTaskByUniqueID(mapTaskID(row.getInteger("pred_task_id"))); if (currentTask != null && predecessorTask != null) { RelationType type = RELATION_TYPE_MAP.get(row.getString("pred_type")); Duration lag = row.getDuration("lag_hr_cnt"); Relation relation = currentTask.addPredecessor(predecessorTask, type, lag); m_eventManager.fireRelationReadEvent(relation); } } } /** * Process assignment data. * * @param rows assignment data */ public void processAssignments(List<Row> rows) { for (Row row : rows) { Task task = m_project.getTaskByUniqueID(mapTaskID(row.getInteger("task_id"))); Resource resource = m_project.getResourceByUniqueID(row.getInteger("rsrc_id")); if (task != null && resource != null) { ResourceAssignment assignment = task.addResourceAssignment(resource); processFields(m_assignmentFields, row, assignment); populateField(assignment, AssignmentField.WORK, AssignmentField.BASELINE_WORK, AssignmentField.ACTUAL_WORK); populateField(assignment, AssignmentField.COST, AssignmentField.BASELINE_COST, AssignmentField.ACTUAL_COST); populateField(assignment, AssignmentField.START, AssignmentField.BASELINE_START, AssignmentField.ACTUAL_START); populateField(assignment, AssignmentField.FINISH, AssignmentField.BASELINE_FINISH, AssignmentField.ACTUAL_FINISH); m_eventManager.fireAssignmentReadEvent(assignment); } } } /** * Code common to both XER and database readers to extract * currency format data. * * @param row row containing currency data */ public void processDefaultCurrency(Row row) { ProjectProperties properties = m_project.getProjectProperties(); properties.setCurrencySymbol(row.getString("curr_symbol")); properties.setSymbolPosition(CURRENCY_SYMBOL_POSITION_MAP.get(row.getString("pos_curr_fmt_type"))); properties.setCurrencyDigits(row.getInteger("decimal_digit_cnt")); properties.setThousandsSeparator(row.getString("digit_group_symbol").charAt(0)); properties.setDecimalSeparator(row.getString("decimal_symbol").charAt(0)); } /** * Generic method to extract Primavera fields and assign to MPXJ fields. * * @param map map of MPXJ field types and Primavera field names * @param row Primavera data container * @param container MPXJ data contain */ private void processFields(Map<FieldType, String> map, Row row, FieldContainer container) { for (Map.Entry<FieldType, String> entry : map.entrySet()) { FieldType field = entry.getKey(); String name = entry.getValue(); Object value; switch (field.getDataType()) { case INTEGER: { value = row.getInteger(name); break; } case BOOLEAN: { value = Boolean.valueOf(row.getBoolean(name)); break; } case DATE: { value = row.getDate(name); break; } case CURRENCY: case NUMERIC: case PERCENTAGE: { value = row.getDouble(name); break; } case DELAY: case WORK: case DURATION: { value = row.getDuration(name); break; } case RESOURCE_TYPE: { value = RESOURCE_TYPE_MAP.get(row.getString(name)); break; } case TASK_TYPE: { value = TASK_TYPE_MAP.get(row.getString(name)); break; } case CONSTRAINT: { value = CONSTRAINT_TYPE_MAP.get(row.getString(name)); break; } case PRIORITY: { value = PRIORITY_MAP.get(row.getString(name)); break; } default: { value = row.getString(name); break; } } container.set(field, value); } } /** * Deals with the case where we have had to map a task ID to a new value. * * @param id task ID from database * @return mapped task ID */ private Integer mapTaskID(Integer id) { Integer mappedID = m_clashMap.get(id); if (mappedID == null) { mappedID = id; } return (mappedID); } /** * Apply aliases to task and resource fields. * * @param aliases map of aliases */ private void applyAliases(Map<FieldType, String> aliases) { CustomFieldContainer fields = m_project.getCustomFields(); for (Map.Entry<FieldType, String> entry : aliases.entrySet()) { fields.getCustomField(entry.getKey()).setAlias(entry.getValue()); } } /** * Determine which type of percent complete is used on on this task, * and calculate the required value. * * @param row task data * @return percent complete value */ private Number calculatePercentComplete(Row row) { Number result; switch (PercentCompleteType.getInstance(row.getString("complete_pct_type"))) { case UNITS: { result = calculateUnitsPercentComplete(row); break; } case DURATION: { result = calculateDurationPercentComplete(row); break; } default: { result = calculatePhysicalPercentComplete(row); break; } } return result; } /** * Calculate the physical percent complete. * * @param row task data * @return percent complete */ private Number calculatePhysicalPercentComplete(Row row) { return row.getDouble("phys_complete_pct"); } /** * Calculate the units percent complete. * * @param row task data * @return percent complete */ private Number calculateUnitsPercentComplete(Row row) { double result = 0; double actualWorkQuantity = NumberHelper.getDouble(row.getDouble("act_work_qty")); double actualEquipmentQuantity = NumberHelper.getDouble(row.getDouble("act_equip_qty")); double numerator = actualWorkQuantity + actualEquipmentQuantity; if (numerator != 0) { double remainingWorkQuantity = NumberHelper.getDouble(row.getDouble("remain_work_qty")); double remainingEquipmentQuantity = NumberHelper.getDouble(row.getDouble("remain_equip_qty")); double denominator = remainingWorkQuantity + actualWorkQuantity + remainingEquipmentQuantity + actualEquipmentQuantity; result = denominator == 0 ? 0 : ((numerator * 100) / denominator); } return NumberHelper.getDouble(result); } /** * Calculate the duration percent complete. * * @param row task data * @return percent complete */ private Number calculateDurationPercentComplete(Row row) { double result = 0; double targetDuration = row.getDuration("target_drtn_hr_cnt").getDuration(); double remainingDuration = row.getDuration("remain_drtn_hr_cnt").getDuration(); if (targetDuration == 0) { if (remainingDuration == 0) { if (row.getString("status_code").equals("TK_Complete")) { result = 100; } } } else { if (remainingDuration < targetDuration) { result = ((targetDuration - remainingDuration) * 100) / targetDuration; } } return NumberHelper.getDouble(result); } /** * Retrieve the default mapping between MPXJ resource fields and Primavera resource field names. * * @return mapping */ public static Map<FieldType, String> getDefaultResourceFieldMap() { Map<FieldType, String> map = new LinkedHashMap<FieldType, String>(); map.put(ResourceField.UNIQUE_ID, "rsrc_id"); map.put(ResourceField.NAME, "rsrc_name"); map.put(ResourceField.CODE, "employee_code"); map.put(ResourceField.EMAIL_ADDRESS, "email_addr"); map.put(ResourceField.NOTES, "rsrc_notes"); map.put(ResourceField.CREATED, "create_date"); map.put(ResourceField.TYPE, "rsrc_type"); map.put(ResourceField.INITIALS, "rsrc_short_name"); map.put(ResourceField.NUMBER1, "parent_rsrc_id"); return map; } /** * Retrieve the default mapping between MPXJ task fields and Primavera wbs field names. * * @return mapping */ public static Map<FieldType, String> getDefaultWbsFieldMap() { Map<FieldType, String> map = new LinkedHashMap<FieldType, String>(); map.put(TaskField.UNIQUE_ID, "wbs_id"); map.put(TaskField.NAME, "wbs_name"); map.put(TaskField.BASELINE_COST, "orig_cost"); map.put(TaskField.REMAINING_COST, "indep_remain_total_cost"); map.put(TaskField.REMAINING_WORK, "indep_remain_work_qty"); map.put(TaskField.BASELINE_START, "anticip_start_date"); map.put(TaskField.BASELINE_FINISH, "anticip_end_date"); map.put(TaskField.DATE1, "suspend_date"); map.put(TaskField.DATE2, "resume_date"); map.put(TaskField.TEXT1, "task_code"); map.put(TaskField.WBS, "wbs_short_name"); return map; } /** * Retrieve the default mapping between MPXJ task fields and Primavera task field names. * * @return mapping */ public static Map<FieldType, String> getDefaultTaskFieldMap() { Map<FieldType, String> map = new LinkedHashMap<FieldType, String>(); map.put(TaskField.UNIQUE_ID, "task_id"); map.put(TaskField.NAME, "task_name"); map.put(TaskField.REMAINING_DURATION, "remain_drtn_hr_cnt"); map.put(TaskField.ACTUAL_WORK, "act_work_qty"); map.put(TaskField.REMAINING_WORK, "remain_work_qty"); map.put(TaskField.BASELINE_WORK, "target_work_qty"); map.put(TaskField.BASELINE_DURATION, "target_drtn_hr_cnt"); map.put(TaskField.CONSTRAINT_DATE, "cstr_date"); map.put(TaskField.ACTUAL_START, "act_start_date"); map.put(TaskField.ACTUAL_FINISH, "act_end_date"); map.put(TaskField.LATE_START, "late_start_date"); map.put(TaskField.LATE_FINISH, "late_end_date"); map.put(TaskField.EARLY_START, "early_start_date"); map.put(TaskField.EARLY_FINISH, "early_end_date"); map.put(TaskField.BASELINE_START, "target_start_date"); map.put(TaskField.BASELINE_FINISH, "target_end_date"); map.put(TaskField.CONSTRAINT_TYPE, "cstr_type"); map.put(TaskField.PRIORITY, "priority_type"); map.put(TaskField.CREATED, "create_date"); map.put(TaskField.TYPE, "duration_type"); map.put(TaskField.FREE_SLACK, "free_float_hr_cnt"); map.put(TaskField.TOTAL_SLACK, "total_float_hr_cnt"); map.put(TaskField.TEXT1, "task_code"); map.put(TaskField.TEXT2, "task_type"); map.put(TaskField.TEXT3, "status_code"); return map; } /** * Retrieve the default mapping between MPXJ assignment fields and Primavera assignment field names. * * @return mapping */ public static Map<FieldType, String> getDefaultAssignmentFieldMap() { Map<FieldType, String> map = new LinkedHashMap<FieldType, String>(); map.put(AssignmentField.UNIQUE_ID, "taskrsrc_id"); map.put(AssignmentField.REMAINING_WORK, "remain_qty"); map.put(AssignmentField.BASELINE_WORK, "target_qty"); map.put(AssignmentField.ACTUAL_WORK, "act_reg_qty"); map.put(AssignmentField.BASELINE_COST, "target_cost"); map.put(AssignmentField.ACTUAL_COST, "act_reg_cost"); map.put(AssignmentField.ACTUAL_START, "act_start_date"); map.put(AssignmentField.ACTUAL_FINISH, "act_end_date"); map.put(AssignmentField.BASELINE_START, "target_start_date"); map.put(AssignmentField.BASELINE_FINISH, "target_end_date"); map.put(AssignmentField.ASSIGNMENT_DELAY, "target_lag_drtn_hr_cnt"); return map; } /** * Retrieve the default aliases to be applied to MPXJ task and resource fields. * * @return map of aliases */ public static Map<FieldType, String> getDefaultAliases() { Map<FieldType, String> map = new HashMap<FieldType, String>(); map.put(ResourceField.NUMBER1, "Parent Resource Unique ID"); map.put(TaskField.DATE1, "Suspend Date"); map.put(TaskField.DATE2, "Resume Date"); map.put(TaskField.TEXT1, "Code"); map.put(TaskField.TEXT2, "Activity Type"); map.put(TaskField.TEXT3, "Status"); return map; } private ProjectFile m_project; private EventManager m_eventManager; private Map<Integer, Integer> m_clashMap = new HashMap<Integer, Integer>(); private Map<Integer, ProjectCalendar> m_calMap = new HashMap<Integer, ProjectCalendar>(); private DateFormat m_calendarTimeFormat = new SimpleDateFormat("HH:mm"); private Map<Integer, String> m_udfMap = new HashMap<Integer, String>(); private final UserFieldCounters m_udfCounters; private Map<FieldType, String> m_resourceFields; private Map<FieldType, String> m_wbsFields; private Map<FieldType, String> m_taskFields; private Map<FieldType, String> m_assignmentFields; private final boolean m_matchPrimaveraWBS; private static final Map<String, ResourceType> RESOURCE_TYPE_MAP = new HashMap<String, ResourceType>(); static { RESOURCE_TYPE_MAP.put(null, ResourceType.WORK); RESOURCE_TYPE_MAP.put("RT_Labor", ResourceType.WORK); RESOURCE_TYPE_MAP.put("RT_Mat", ResourceType.MATERIAL); RESOURCE_TYPE_MAP.put("RT_Equip", ResourceType.WORK); } private static final Map<String, ConstraintType> CONSTRAINT_TYPE_MAP = new HashMap<String, ConstraintType>(); static { CONSTRAINT_TYPE_MAP.put("CS_MSO", ConstraintType.MUST_START_ON); CONSTRAINT_TYPE_MAP.put("CS_MSOB", ConstraintType.START_NO_LATER_THAN); CONSTRAINT_TYPE_MAP.put("CS_MSOA", ConstraintType.START_NO_EARLIER_THAN); CONSTRAINT_TYPE_MAP.put("CS_MEO", ConstraintType.MUST_FINISH_ON); CONSTRAINT_TYPE_MAP.put("CS_MEOB", ConstraintType.FINISH_NO_LATER_THAN); CONSTRAINT_TYPE_MAP.put("CS_MEOA", ConstraintType.FINISH_NO_EARLIER_THAN); CONSTRAINT_TYPE_MAP.put("CS_ALAP", ConstraintType.AS_LATE_AS_POSSIBLE); CONSTRAINT_TYPE_MAP.put("CS_MANDSTART", ConstraintType.MUST_START_ON); CONSTRAINT_TYPE_MAP.put("CS_MANDFIN", ConstraintType.MUST_FINISH_ON); } private static final Map<String, Priority> PRIORITY_MAP = new HashMap<String, Priority>(); static { PRIORITY_MAP.put("PT_Top", Priority.getInstance(Priority.HIGHEST)); PRIORITY_MAP.put("PT_High", Priority.getInstance(Priority.HIGH)); PRIORITY_MAP.put("PT_Normal", Priority.getInstance(Priority.MEDIUM)); PRIORITY_MAP.put("PT_Low", Priority.getInstance(Priority.LOW)); PRIORITY_MAP.put("PT_Lowest", Priority.getInstance(Priority.LOWEST)); } private static final Map<String, RelationType> RELATION_TYPE_MAP = new HashMap<String, RelationType>(); static { RELATION_TYPE_MAP.put("PR_FS", RelationType.FINISH_START); RELATION_TYPE_MAP.put("PR_FF", RelationType.FINISH_FINISH); RELATION_TYPE_MAP.put("PR_SS", RelationType.START_START); RELATION_TYPE_MAP.put("PR_SF", RelationType.START_FINISH); } private static final Map<String, TaskType> TASK_TYPE_MAP = new HashMap<String, TaskType>(); static { TASK_TYPE_MAP.put("DT_FixedDrtn", TaskType.FIXED_DURATION); TASK_TYPE_MAP.put("DT_FixedQty", TaskType.FIXED_UNITS); TASK_TYPE_MAP.put("DT_FixedDUR2", TaskType.FIXED_WORK); TASK_TYPE_MAP.put("DT_FixedRate", TaskType.FIXED_WORK); } private static final Map<String, Boolean> MILESTONE_MAP = new HashMap<String, Boolean>(); static { MILESTONE_MAP.put("TT_Task", Boolean.FALSE); MILESTONE_MAP.put("TT_Rsrc", Boolean.FALSE); MILESTONE_MAP.put("TT_LOE", Boolean.FALSE); MILESTONE_MAP.put("TT_Mile", Boolean.TRUE); MILESTONE_MAP.put("TT_FinMile", Boolean.TRUE); MILESTONE_MAP.put("TT_WBS", Boolean.FALSE); } private static final Map<String, CurrencySymbolPosition> CURRENCY_SYMBOL_POSITION_MAP = new HashMap<String, CurrencySymbolPosition>(); static { CURRENCY_SYMBOL_POSITION_MAP.put("#1.1", CurrencySymbolPosition.BEFORE); CURRENCY_SYMBOL_POSITION_MAP.put("1.1#", CurrencySymbolPosition.AFTER); CURRENCY_SYMBOL_POSITION_MAP.put("# 1.1", CurrencySymbolPosition.BEFORE_WITH_SPACE); CURRENCY_SYMBOL_POSITION_MAP.put("1.1 #", CurrencySymbolPosition.AFTER_WITH_SPACE); } }