/* * file: MPXWriter.java * author: Jon Iles * copyright: (c) Packwood Software 2006 * date: 03/01/2006 */ /* * 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.mpx; import java.io.BufferedOutputStream; import java.io.IOException; import java.io.OutputStream; import java.io.OutputStreamWriter; import java.util.Calendar; import java.util.Date; import java.util.List; import java.util.Locale; import net.sf.mpxj.AccrueType; import net.sf.mpxj.ConstraintType; 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.Priority; import net.sf.mpxj.ProjectCalendar; import net.sf.mpxj.ProjectCalendarException; import net.sf.mpxj.ProjectCalendarHours; import net.sf.mpxj.ProjectFile; import net.sf.mpxj.ProjectProperties; import net.sf.mpxj.Rate; import net.sf.mpxj.RecurringTask; import net.sf.mpxj.Relation; import net.sf.mpxj.RelationType; import net.sf.mpxj.Resource; import net.sf.mpxj.ResourceAssignment; import net.sf.mpxj.ResourceAssignmentWorkgroupFields; import net.sf.mpxj.ResourceField; import net.sf.mpxj.Task; import net.sf.mpxj.TaskField; import net.sf.mpxj.TaskType; import net.sf.mpxj.TimeUnit; import net.sf.mpxj.common.NumberHelper; import net.sf.mpxj.writer.AbstractProjectWriter; /** * This class creates a new MPX file from the contents of * a ProjectFile instance. */ public final class MPXWriter extends AbstractProjectWriter { /** * {@inheritDoc} */ @Override public void write(ProjectFile projectFile, OutputStream out) throws IOException { m_projectFile = projectFile; m_eventManager = projectFile.getEventManager(); if (m_useLocaleDefaults == true) { LocaleUtility.setLocale(m_projectFile.getProjectProperties(), m_locale); } m_delimiter = projectFile.getProjectProperties().getMpxDelimiter(); m_writer = new OutputStreamWriter(new BufferedOutputStream(out), projectFile.getProjectProperties().getMpxCodePage().getCharset()); m_buffer = new StringBuilder(); m_formats = new MPXJFormats(m_locale, LocaleData.getString(m_locale, LocaleData.NA), m_projectFile); try { write(); } finally { m_writer = null; m_projectFile = null; m_resourceModel = null; m_taskModel = null; m_buffer = null; m_locale = null; m_formats = null; } } /** * Writes the contents of the project file as MPX records. * * @throws IOException */ private void write() throws IOException { m_projectFile.validateUniqueIDsForMicrosoftProject(); writeFileCreationRecord(); writeProjectHeader(m_projectFile.getProjectProperties()); if (m_projectFile.getAllResources().isEmpty() == false) { m_resourceModel = new ResourceModel(m_projectFile, m_locale); m_writer.write(m_resourceModel.toString()); for (Resource resource : m_projectFile.getAllResources()) { writeResource(resource); } } if (m_projectFile.getAllTasks().isEmpty() == false) { m_taskModel = new TaskModel(m_projectFile, m_locale); m_writer.write(m_taskModel.toString()); writeTasks(m_projectFile.getChildTasks()); } m_writer.flush(); } /** * Write file creation record. * * @throws IOException */ private void writeFileCreationRecord() throws IOException { ProjectProperties properties = m_projectFile.getProjectProperties(); m_buffer.setLength(0); m_buffer.append("MPX"); m_buffer.append(m_delimiter); m_buffer.append(properties.getMpxProgramName()); m_buffer.append(m_delimiter); m_buffer.append(properties.getMpxFileVersion()); m_buffer.append(m_delimiter); m_buffer.append(properties.getMpxCodePage()); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Write project header. * * @param properties project properties * @throws IOException */ private void writeProjectHeader(ProjectProperties properties) throws IOException { m_buffer.setLength(0); // // Currency Settings Record // m_buffer.append(MPXConstants.CURRENCY_SETTINGS_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getCurrencySymbol())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getSymbolPosition())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getCurrencyDigits())); m_buffer.append(m_delimiter); m_buffer.append(format(Character.valueOf(properties.getThousandsSeparator()))); m_buffer.append(m_delimiter); m_buffer.append(format(Character.valueOf(properties.getDecimalSeparator()))); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); // // Default Settings Record // m_buffer.append(MPXConstants.DEFAULT_SETTINGS_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(format(Integer.valueOf(properties.getDefaultDurationUnits().getValue()))); m_buffer.append(m_delimiter); m_buffer.append(properties.getDefaultDurationIsFixed() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(format(Integer.valueOf(properties.getDefaultWorkUnits().getValue()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDecimal(NumberHelper.getDouble(properties.getMinutesPerDay()) / 60))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDecimal(NumberHelper.getDouble(properties.getMinutesPerWeek()) / 60))); m_buffer.append(m_delimiter); m_buffer.append(format(formatRate(properties.getDefaultStandardRate()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatRate(properties.getDefaultOvertimeRate()))); m_buffer.append(m_delimiter); m_buffer.append(properties.getUpdatingTaskStatusUpdatesResourceStatus() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(properties.getSplitInProgressTasks() ? "1" : "0"); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); // // Date Time Settings Record // m_buffer.append(MPXConstants.DATE_TIME_SETTINGS_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getDateOrder())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getTimeFormat())); m_buffer.append(m_delimiter); m_buffer.append(format(getIntegerTimeInMinutes(properties.getDefaultStartTime()))); m_buffer.append(m_delimiter); m_buffer.append(format(Character.valueOf(properties.getDateSeparator()))); m_buffer.append(m_delimiter); m_buffer.append(format(Character.valueOf(properties.getTimeSeparator()))); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getAMText())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getPMText())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getDateFormat())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getBarTextDateFormat())); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); // // Write project calendars // for (ProjectCalendar cal : m_projectFile.getCalendars()) { writeCalendar(cal); } // // Project Header Record // m_buffer.setLength(0); m_buffer.append(MPXConstants.PROJECT_HEADER_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getProjectTitle())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getCompany())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getManager())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getDefaultCalendarName())); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getStartDate()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getFinishDate()))); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getScheduleFrom())); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getCurrentDate()))); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getComments())); m_buffer.append(m_delimiter); m_buffer.append(format(formatCurrency(properties.getCost()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatCurrency(properties.getBaselineCost()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatCurrency(properties.getActualCost()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getBaselineWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getActualWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatPercentage(properties.getWork2()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getDuration()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getBaselineDuration()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getActualDuration()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatPercentage(properties.getPercentageComplete()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getBaselineStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getBaselineFinish()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getActualStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(properties.getActualFinish()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getStartVariance()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(properties.getFinishVariance()))); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getSubject())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getAuthor())); m_buffer.append(m_delimiter); m_buffer.append(format(properties.getKeywords())); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Write a calendar. * * @param record calendar instance * @throws IOException */ private void writeCalendar(ProjectCalendar record) throws IOException { // // Test used to ensure that we don't write the default calendar used for the "Unassigned" resource // if (record.getParent() == null || record.getResource() != null) { m_buffer.setLength(0); if (record.getParent() == null) { m_buffer.append(MPXConstants.BASE_CALENDAR_RECORD_NUMBER); m_buffer.append(m_delimiter); if (record.getName() != null) { m_buffer.append(record.getName()); } } else { m_buffer.append(MPXConstants.RESOURCE_CALENDAR_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(record.getParent().getName()); } DayType[] days = record.getDays(); for (int loop = 0; loop < days.length; loop++) { m_buffer.append(m_delimiter); m_buffer.append(days[loop].getValue()); } m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); ProjectCalendarHours[] hours = record.getHours(); for (int loop = 0; loop < hours.length; loop++) { if (hours[loop] != null) { writeCalendarHours(record, hours[loop]); } } if (!record.getCalendarExceptions().isEmpty()) { // // A quirk of MS Project is that these exceptions must be // in date order in the file, otherwise they are ignored. // The getCalendarExceptions method now guarantees that // the exceptions list is sorted when retrieved. // for (ProjectCalendarException ex : record.getCalendarExceptions()) { writeCalendarException(record, ex); } } m_eventManager.fireCalendarWrittenEvent(record); } } /** * Write calendar hours. * * @param parentCalendar parent calendar instance * @param record calendar hours instance * @throws IOException */ private void writeCalendarHours(ProjectCalendar parentCalendar, ProjectCalendarHours record) throws IOException { m_buffer.setLength(0); int recordNumber; if (!parentCalendar.isDerived()) { recordNumber = MPXConstants.BASE_CALENDAR_HOURS_RECORD_NUMBER; } else { recordNumber = MPXConstants.RESOURCE_CALENDAR_HOURS_RECORD_NUMBER; } DateRange range1 = record.getRange(0); if (range1 == null) { range1 = DateRange.EMPTY_RANGE; } DateRange range2 = record.getRange(1); if (range2 == null) { range2 = DateRange.EMPTY_RANGE; } DateRange range3 = record.getRange(2); if (range3 == null) { range3 = DateRange.EMPTY_RANGE; } m_buffer.append(recordNumber); m_buffer.append(m_delimiter); m_buffer.append(format(record.getDay())); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(range1.getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(range1.getEnd()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(range2.getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(range2.getEnd()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(range3.getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(range3.getEnd()))); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Write a calendar exception. * * @param parentCalendar parent calendar instance * @param record calendar exception instance * @throws IOException */ private void writeCalendarException(ProjectCalendar parentCalendar, ProjectCalendarException record) throws IOException { m_buffer.setLength(0); if (!parentCalendar.isDerived()) { m_buffer.append(MPXConstants.BASE_CALENDAR_EXCEPTION_RECORD_NUMBER); } else { m_buffer.append(MPXConstants.RESOURCE_CALENDAR_EXCEPTION_RECORD_NUMBER); } m_buffer.append(m_delimiter); m_buffer.append(format(formatDate(record.getFromDate()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDate(record.getToDate()))); m_buffer.append(m_delimiter); m_buffer.append(record.getWorking() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(record.getRange(0).getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(record.getRange(0).getEnd()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(record.getRange(1).getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(record.getRange(1).getEnd()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(record.getRange(2).getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatTime(record.getRange(2).getEnd()))); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Write a resource. * * @param record resource instance * @throws IOException */ private void writeResource(Resource record) throws IOException { m_buffer.setLength(0); // // Write the resource record // int[] fields = m_resourceModel.getModel(); m_buffer.append(MPXConstants.RESOURCE_RECORD_NUMBER); for (int loop = 0; loop < fields.length; loop++) { int mpxFieldType = fields[loop]; if (mpxFieldType == -1) { break; } ResourceField resourceField = MPXResourceField.getMpxjField(mpxFieldType); Object value = record.getCachedValue(resourceField); value = formatType(resourceField.getDataType(), value); m_buffer.append(m_delimiter); m_buffer.append(format(value)); } stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); // // Write the resource notes // String notes = record.getNotes(); if (notes.length() != 0) { writeNotes(MPXConstants.RESOURCE_NOTES_RECORD_NUMBER, notes); } // // Write the resource calendar // if (record.getResourceCalendar() != null) { writeCalendar(record.getResourceCalendar()); } m_eventManager.fireResourceWrittenEvent(record); } /** * Write notes. * * @param recordNumber record number * @param text note text * @throws IOException */ private void writeNotes(int recordNumber, String text) throws IOException { m_buffer.setLength(0); m_buffer.append(recordNumber); m_buffer.append(m_delimiter); if (text != null) { String note = stripLineBreaks(text, MPXConstants.EOL_PLACEHOLDER_STRING); boolean quote = (note.indexOf(m_delimiter) != -1 || note.indexOf('"') != -1); int length = note.length(); char c; if (quote == true) { m_buffer.append('"'); } for (int loop = 0; loop < length; loop++) { c = note.charAt(loop); switch (c) { case '"': { m_buffer.append("\"\""); break; } default: { m_buffer.append(c); break; } } } if (quote == true) { m_buffer.append('"'); } } m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Write a task. * * @param record task instance * @throws IOException */ private void writeTask(Task record) throws IOException { m_buffer.setLength(0); // // Write the task // int[] fields = m_taskModel.getModel(); int field; m_buffer.append(MPXConstants.TASK_RECORD_NUMBER); for (int loop = 0; loop < fields.length; loop++) { field = fields[loop]; if (field == -1) { break; } TaskField taskField = MPXTaskField.getMpxjField(field); Object value = record.getCachedValue(taskField); value = formatType(taskField.getDataType(), value); m_buffer.append(m_delimiter); m_buffer.append(format(value)); } stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); // // Write the task notes // String notes = record.getNotes(); if (notes.length() != 0) { writeNotes(MPXConstants.TASK_NOTES_RECORD_NUMBER, notes); } // // Write the recurring task // if (record.getRecurringTask() != null) { writeRecurringTask(record.getRecurringTask()); } // // Write any resource assignments // if (record.getResourceAssignments().isEmpty() == false) { for (ResourceAssignment assignment : record.getResourceAssignments()) { writeResourceAssignment(assignment); } } m_eventManager.fireTaskWrittenEvent(record); } /** * Write a recurring task. * * @param record recurring task instance * @throws IOException */ private void writeRecurringTask(RecurringTask record) throws IOException { m_buffer.setLength(0); m_buffer.append(MPXConstants.RECURRING_TASK_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append("1"); if (record.getRecurrenceType() != null) { m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(record.getStartDate()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(record.getFinishDate()))); m_buffer.append(m_delimiter); m_buffer.append(format(RecurrenceUtility.getDurationValue(m_projectFile.getProjectProperties(), record.getDuration()))); m_buffer.append(m_delimiter); m_buffer.append(format(RecurrenceUtility.getDurationUnits(record))); m_buffer.append(m_delimiter); m_buffer.append(format(record.getOccurrences())); m_buffer.append(m_delimiter); m_buffer.append(format(RecurrenceUtility.getRecurrenceValue(record.getRecurrenceType()))); m_buffer.append(m_delimiter); m_buffer.append("0"); m_buffer.append(m_delimiter); m_buffer.append(record.getUseEndDate() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(record.getDailyWorkday() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(format(RecurrenceUtility.getDays(record.getWeeklyDays()))); m_buffer.append(m_delimiter); m_buffer.append(record.getMonthlyRelative() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(record.getYearlyAbsolute() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(format(record.getDailyFrequency())); m_buffer.append(m_delimiter); m_buffer.append(format(record.getWeeklyFrequency())); m_buffer.append(m_delimiter); m_buffer.append(format(record.getMonthlyRelativeOrdinal())); m_buffer.append(m_delimiter); m_buffer.append(format(RecurrenceUtility.getDay(record.getMonthlyRelativeDay()))); m_buffer.append(m_delimiter); m_buffer.append(format(record.getMonthlyRelativeFrequency())); m_buffer.append(m_delimiter); m_buffer.append(format(record.getMonthlyAbsoluteDay())); m_buffer.append(m_delimiter); m_buffer.append(format(record.getMonthlyAbsoluteFrequency())); m_buffer.append(m_delimiter); m_buffer.append(format(record.getYearlyRelativeOrdinal())); m_buffer.append(m_delimiter); m_buffer.append(format(RecurrenceUtility.getDay(record.getYearlyRelativeDay()))); m_buffer.append(m_delimiter); m_buffer.append(format(record.getYearlyRelativeMonth())); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(record.getYearlyAbsoluteDate()))); stripTrailingDelimiters(m_buffer); } m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Write resource assignment. * * @param record resource assignment instance * @throws IOException */ private void writeResourceAssignment(ResourceAssignment record) throws IOException { m_buffer.setLength(0); m_buffer.append(MPXConstants.RESOURCE_ASSIGNMENT_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(formatResource(record.getResource())); m_buffer.append(m_delimiter); m_buffer.append(format(formatUnits(record.getUnits()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(record.getWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(record.getBaselineWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(record.getActualWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(record.getOvertimeWork()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatCurrency(record.getCost()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatCurrency(record.getBaselineCost()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatCurrency(record.getActualCost()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(record.getStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTime(record.getFinish()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDuration(record.getDelay()))); m_buffer.append(m_delimiter); m_buffer.append(format(record.getResourceUniqueID())); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); ResourceAssignmentWorkgroupFields workgroup = record.getWorkgroupAssignment(); if (workgroup == null) { workgroup = ResourceAssignmentWorkgroupFields.EMPTY; } writeResourceAssignmentWorkgroupFields(workgroup); m_eventManager.fireAssignmentWrittenEvent(record); } /** * Write resource assignment workgroup. * * @param record resource assignment workgroup instance * @throws IOException */ private void writeResourceAssignmentWorkgroupFields(ResourceAssignmentWorkgroupFields record) throws IOException { m_buffer.setLength(0); m_buffer.append(MPXConstants.RESOURCE_ASSIGNMENT_WORKGROUP_FIELDS_RECORD_NUMBER); m_buffer.append(m_delimiter); m_buffer.append(format(record.getMessageUniqueID())); m_buffer.append(m_delimiter); m_buffer.append(record.getConfirmed() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(record.getResponsePending() ? "1" : "0"); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTimeNull(record.getUpdateStart()))); m_buffer.append(m_delimiter); m_buffer.append(format(formatDateTimeNull(record.getUpdateFinish()))); m_buffer.append(m_delimiter); m_buffer.append(format(record.getScheduleID())); stripTrailingDelimiters(m_buffer); m_buffer.append(MPXConstants.EOL); m_writer.write(m_buffer.toString()); } /** * Recursively write tasks. * * @param tasks list of tasks * @throws IOException */ private void writeTasks(List<Task> tasks) throws IOException { for (Task task : tasks) { writeTask(task); writeTasks(task.getChildTasks()); } } /** * This internal method is used to convert from a Date instance to an * integer representing the number of minutes past midnight. * * @param date date instance * @return minutes past midnight as an integer */ private Integer getIntegerTimeInMinutes(Date date) { Integer result = null; if (date != null) { Calendar cal = Calendar.getInstance(); cal.setTime(date); int time = cal.get(Calendar.HOUR_OF_DAY) * 60; time += cal.get(Calendar.MINUTE); result = Integer.valueOf(time); } return (result); } /** * This method is called when double quotes are found as part of * a value. The quotes are escaped by adding a second quote character * and the entire value is quoted. * * @param value text containing quote characters * @return escaped and quoted text */ private String escapeQuotes(String value) { StringBuilder sb = new StringBuilder(); int length = value.length(); char c; sb.append('"'); for (int index = 0; index < length; index++) { c = value.charAt(index); sb.append(c); if (c == '"') { sb.append('"'); } } sb.append('"'); return (sb.toString()); } /** * This method removes line breaks from a piece of text, and replaces * them with the supplied text. * * @param text source text * @param replacement line break replacement text * @return text with line breaks removed. */ private String stripLineBreaks(String text, String replacement) { if (text.indexOf('\r') != -1 || text.indexOf('\n') != -1) { StringBuilder sb = new StringBuilder(text); int index; while ((index = sb.indexOf("\r\n")) != -1) { sb.replace(index, index + 2, replacement); } while ((index = sb.indexOf("\n\r")) != -1) { sb.replace(index, index + 2, replacement); } while ((index = sb.indexOf("\r")) != -1) { sb.replace(index, index + 1, replacement); } while ((index = sb.indexOf("\n")) != -1) { sb.replace(index, index + 1, replacement); } text = sb.toString(); } return (text); } /** * This method returns the string representation of an object. In most * cases this will simply involve calling the normal toString method * on the object, but a couple of exceptions are handled here. * * @param o the object to formatted * @return formatted string representing input Object */ private String format(Object o) { String result; if (o == null) { result = ""; } else { if (o instanceof Boolean == true) { result = LocaleData.getString(m_locale, (((Boolean) o).booleanValue() == true ? LocaleData.YES : LocaleData.NO)); } else { if (o instanceof Float == true || o instanceof Double == true) { result = (m_formats.getDecimalFormat().format(((Number) o).doubleValue())); } else { if (o instanceof Day) { result = Integer.toString(((Day) o).getValue()); } else { result = o.toString(); } } } // // At this point there should be no line break characters in // the file. If we find any, replace them with spaces // result = stripLineBreaks(result, MPXConstants.EOL_PLACEHOLDER_STRING); // // Finally we check to ensure that there are no embedded // quotes or separator characters in the value. If there are, then // we quote the value and escape any existing quote characters. // if (result.indexOf('"') != -1) { result = escapeQuotes(result); } else { if (result.indexOf(m_delimiter) != -1) { result = '"' + result + '"'; } } } return (result); } /** * This method removes trailing delimiter characters. * * @param buffer input sring buffer */ private void stripTrailingDelimiters(StringBuilder buffer) { int index = buffer.length() - 1; while (index > 0 && buffer.charAt(index) == m_delimiter) { --index; } buffer.setLength(index + 1); } /** * This method is called to format a time value. * * @param value time value * @return formatted time value */ private String formatTime(Date value) { return (value == null ? null : m_formats.getTimeFormat().format(value)); } /** * This method is called to format a currency value. * * @param value numeric value * @return currency value */ private String formatCurrency(Number value) { return (value == null ? null : m_formats.getCurrencyFormat().format(value)); } /** * This method is called to format a units value. * * @param value numeric value * @return currency value */ private String formatUnits(Number value) { return (value == null ? null : m_formats.getUnitsDecimalFormat().format(value.doubleValue() / 100)); } /** * This method is called to format a date. * * @param value date value * @return formatted date value */ private String formatDateTime(Object value) { String result = null; if (value instanceof Date) { result = m_formats.getDateTimeFormat().format(value); } return result; } /** * This method is called to format a date. It will return the null text * if a null value is supplied. * * @param value date value * @return formatted date value */ private String formatDateTimeNull(Date value) { return (value == null ? m_formats.getNullText() : m_formats.getDateTimeFormat().format(value)); } /** * This method is called to format a date. * * @param value date value * @return formatted date value */ private String formatDate(Date value) { return (value == null ? null : m_formats.getDateFormat().format(value)); } /** * This method is called to format a percentage value. * * @param value numeric value * @return percentage value */ private String formatPercentage(Number value) { return (value == null ? null : m_formats.getPercentageDecimalFormat().format(value) + "%"); } /** * This method is called to format an accrue type value. * * @param type accrue type * @return formatted accrue type */ private String formatAccrueType(AccrueType type) { return (type == null ? null : LocaleData.getStringArray(m_locale, LocaleData.ACCRUE_TYPES)[type.getValue() - 1]); } /** * This method is called to format a constraint type. * * @param type constraint type * @return formatted constraint type */ private String formatConstraintType(ConstraintType type) { return (type == null ? null : LocaleData.getStringArray(m_locale, LocaleData.CONSTRAINT_TYPES)[type.getValue()]); } /** * This method is called to format a duration. * * @param value duration value * @return formatted duration value */ private String formatDuration(Object value) { String result = null; if (value instanceof Duration) { Duration duration = (Duration) value; result = m_formats.getDurationDecimalFormat().format(duration.getDuration()) + formatTimeUnit(duration.getUnits()); } return result; } /** * This method is called to format a rate. * * @param value rate value * @return formatted rate */ private String formatRate(Rate value) { String result = null; if (value != null) { StringBuilder buffer = new StringBuilder(m_formats.getCurrencyFormat().format(value.getAmount())); buffer.append("/"); buffer.append(formatTimeUnit(value.getUnits())); result = buffer.toString(); } return (result); } /** * This method is called to format a priority. * * @param value priority instance * @return formatted priority value */ private String formatPriority(Priority value) { String result = null; if (value != null) { String[] priorityTypes = LocaleData.getStringArray(m_locale, LocaleData.PRIORITY_TYPES); int priority = value.getValue(); if (priority < Priority.LOWEST) { priority = Priority.LOWEST; } else { if (priority > Priority.DO_NOT_LEVEL) { priority = Priority.DO_NOT_LEVEL; } } priority /= 100; result = priorityTypes[priority - 1]; } return (result); } /** * This method is called to format a task type. * * @param value task type value * @return formatted task type */ private String formatTaskType(TaskType value) { return (LocaleData.getString(m_locale, (value == TaskType.FIXED_DURATION ? LocaleData.YES : LocaleData.NO))); } /** * This method is called to format a relation list. * * @param value relation list instance * @return formatted relation list */ private String formatRelationList(List<Relation> value) { String result = null; if (value != null) { StringBuilder sb = new StringBuilder(); for (Relation relation : value) { if (sb.length() != 0) { sb.append(m_delimiter); } sb.append(formatRelation(relation)); } result = sb.toString(); } return (result); } /** * This method is called to format a relation. * * @param relation relation instance * @return formatted relation instance */ private String formatRelation(Relation relation) { String result = null; if (relation != null) { StringBuilder sb = new StringBuilder(relation.getTargetTask().getID().toString()); Duration duration = relation.getLag(); RelationType type = relation.getType(); double durationValue = duration.getDuration(); if ((durationValue != 0) || (type != RelationType.FINISH_START)) { String[] typeNames = LocaleData.getStringArray(m_locale, LocaleData.RELATION_TYPES); sb.append(typeNames[type.getValue()]); } if (durationValue != 0) { if (durationValue > 0) { sb.append('+'); } sb.append(formatDuration(duration)); } result = sb.toString(); } m_eventManager.fireRelationWrittenEvent(relation); return (result); } /** * This method formats a time unit. * * @param timeUnit time unit instance * @return formatted time unit instance */ private String formatTimeUnit(TimeUnit timeUnit) { int units = timeUnit.getValue(); String result; String[][] unitNames = LocaleData.getStringArrays(m_locale, LocaleData.TIME_UNITS_ARRAY); if (units < 0 || units >= unitNames.length) { result = ""; } else { result = unitNames[units][0]; } return (result); } /** * This method formats a decimal value. * * @param value value * @return formatted value */ private String formatDecimal(double value) { return (m_formats.getDecimalFormat().format(value)); } /** * Converts a value to the appropriate type. * * @param type target type * @param value input value * @return output value */ @SuppressWarnings("unchecked") private Object formatType(DataType type, Object value) { switch (type) { case DATE: { value = formatDateTime(value); break; } case CURRENCY: { value = formatCurrency((Number) value); break; } case UNITS: { value = formatUnits((Number) value); break; } case PERCENTAGE: { value = formatPercentage((Number) value); break; } case ACCRUE: { value = formatAccrueType((AccrueType) value); break; } case CONSTRAINT: { value = formatConstraintType((ConstraintType) value); break; } case WORK: case DURATION: { value = formatDuration(value); break; } case RATE: { value = formatRate((Rate) value); break; } case PRIORITY: { value = formatPriority((Priority) value); break; } case RELATION_LIST: { value = formatRelationList((List<Relation>) value); break; } case TASK_TYPE: { value = formatTaskType((TaskType) value); break; } default: { break; } } return (value); } /** * Formats a resource, taking into account that the resource reference * may be null. * * @param resource Resource instance * @return formatted value */ private String formatResource(Resource resource) { return (resource == null ? "-65535" : format(resource.getID())); } /** * This method returns the locale used by this MPX file. * * @return current locale */ public Locale getLocale() { return (m_locale); } /** * This method sets the locale to be used by this MPX file. * * @param locale locale to be used */ public void setLocale(Locale locale) { m_locale = locale; } /** * Retrieves a flag indicating if the default settings for the locale should * override any project settings. * * @return boolean flag. */ public boolean getUseLocaleDefaults() { return m_useLocaleDefaults; } /** * Sets a flag indicating if the default settings for the locale should * override any project settings. * * @param useLocaleDefaults boolean flag */ public void setUseLocaleDefaults(boolean useLocaleDefaults) { m_useLocaleDefaults = useLocaleDefaults; } /** * Retrieves an array of locales supported by this class. * * @return array of supported locales */ public Locale[] getSupportedLocales() { return (LocaleUtility.getSupportedLocales()); } private ProjectFile m_projectFile; private EventManager m_eventManager; private OutputStreamWriter m_writer; private ResourceModel m_resourceModel; private TaskModel m_taskModel; private char m_delimiter; private Locale m_locale = Locale.ENGLISH; private boolean m_useLocaleDefaults = true; private StringBuilder m_buffer; private MPXJFormats m_formats; }