
net.sf.mpxj.ProjectFile Maven / Gradle / Ivy
/*
* file: ProjectFile.java
* author: Jon Iles
* copyright: (c) Packwood Software 2002-2006
* date: 15/08/2002
*/
/*
* 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;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.Map;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.Set;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import net.sf.mpxj.common.NumberHelper;
import net.sf.mpxj.common.ObjectSequence;
/**
* This class represents a project plan.
*/
public final class ProjectFile implements ChildTaskContainer, ChildResourceContainer, UniqueIdObjectSequenceProvider
{
/**
* Default constructor.
*/
public ProjectFile()
{
m_shared = new ProjectFileSharedData();
}
/**
* Constructor allowing a ProjectFileSharedData instance to be passed.
*
* @param shared ProjectFileSharedData instance
*/
public ProjectFile(ProjectFileSharedData shared)
{
m_shared = shared;
}
/**
* Retrieve project configuration data.
*
* @return ProjectConfig instance.
*/
public ProjectConfig getProjectConfig()
{
return m_config;
}
/**
* This method allows a task to be added to the file programmatically.
*
* @return new task object
*/
@Override public Task addTask()
{
return m_tasks.add();
}
/**
* This method is used to remove a task from the project.
*
* @param task task to be removed
*/
public void removeTask(Task task)
{
m_tasks.remove(task);
}
/**
* This method is used to retrieve a list of all top level tasks
* defined in this project file.
*
* @return list of tasks
*/
@Override public List getChildTasks()
{
return m_childTasks;
}
/**
* This method is used to retrieve a list of all top level resources
* defined in this project file.
*
* @return list of resources
*/
@Override public List getChildResources()
{
return m_childResources;
}
/**
* This method is used to retrieve a list of all tasks
* defined in this project file.
*
* @return list of all tasks
*/
public TaskContainer getTasks()
{
return m_tasks;
}
/**
* This method is used to add a new calendar to the file.
*
* @return new calendar object
*/
public ProjectCalendar addCalendar()
{
return m_calendars.add();
}
/**
* Removes a calendar.
*
* @param calendar calendar to be removed
*/
public void removeCalendar(ProjectCalendar calendar)
{
m_calendars.remove(calendar);
}
/**
* This is a convenience method used to add a calendar called
* "Standard" to the file, and populate it with a default working week
* and default working hours.
*
* @return a new default calendar
*/
public ProjectCalendar addDefaultBaseCalendar()
{
return m_calendars.addDefaultBaseCalendar();
}
/**
* This is a convenience method to add a default derived
* calendar.
*
* @return new ProjectCalendar instance
*/
public ProjectCalendar addDefaultDerivedCalendar()
{
return m_calendars.addDefaultDerivedCalendar();
}
/**
* This method retrieves the list of calendars defined in
* this file.
*
* @return list of calendars
*/
public ProjectCalendarContainer getCalendars()
{
return m_calendars;
}
/**
* This method is used to retrieve the project properties.
*
* @return project properties
*/
public ProjectProperties getProjectProperties()
{
return m_properties;
}
/**
* This method is used to add a new resource to the file.
*
* @return new resource object
*/
@Override public Resource addResource()
{
return m_resources.add();
}
/**
* This method is used to remove a resource from the project.
*
* @param resource resource to be removed
*/
public void removeResource(Resource resource)
{
m_resources.remove(resource);
}
/**
* Retrieves a list of all resources in this project.
*
* @return list of all resources
*/
public ResourceContainer getResources()
{
return m_resources;
}
/**
* Retrieves a list of all resource assignments in this project.
*
* @return list of all resources
*/
public ResourceAssignmentContainer getResourceAssignments()
{
return m_assignments;
}
/**
* Retrieves a list of all relations in this project.
*
* @return list of all relations
*/
public RelationContainer getRelations()
{
return m_relations;
}
/**
* Retrieves the named calendar. This method will return
* null if the named calendar is not located.
*
* @param calendarName name of the required calendar
* @return ProjectCalendar instance
*/
public ProjectCalendar getCalendarByName(String calendarName)
{
return m_calendars.getByName(calendarName);
}
/**
* Retrieves the calendar referred to by the supplied unique ID
* value. This method will return null if the required calendar is not
* located.
*
* @param calendarID calendar unique ID
* @return ProjectCalendar instance
*/
public ProjectCalendar getCalendarByUniqueID(Integer calendarID)
{
return m_calendars.getByUniqueID(calendarID);
}
/**
* This method allows an arbitrary task to be retrieved based
* on its ID field.
*
* @param id task identified
* @return the requested task, or null if not found
*/
public Task getTaskByID(Integer id)
{
return m_tasks.getByID(id);
}
/**
* This method allows an arbitrary task to be retrieved based
* on its UniqueID field.
*
* @param id task identified
* @return the requested task, or null if not found
*/
public Task getTaskByUniqueID(Integer id)
{
return m_tasks.getByUniqueID(id);
}
/**
* This method allows an arbitrary resource to be retrieved based
* on its ID field.
*
* @param id resource identified
* @return the requested resource, or null if not found
*/
public Resource getResourceByID(Integer id)
{
return m_resources.getByID(id);
}
/**
* This method allows an arbitrary resource to be retrieved based
* on its UniqueID field.
*
* @param id resource identified
* @return the requested resource, or null if not found
*/
public Resource getResourceByUniqueID(Integer id)
{
return m_resources.getByUniqueID(id);
}
/**
* This method is used to recreate the hierarchical structure of the
* project file from scratch. The method sorts the list of all tasks,
* then iterates through it creating the parent-child structure defined
* by the outline level field.
*/
public void updateStructure()
{
m_tasks.updateStructure();
m_resources.updateStructure();
}
/**
* Find the earliest task start date.
*
* @return start date
*/
public LocalDateTime getEarliestStartDate()
{
LocalDateTime startDate = null;
for (Task task : m_tasks)
{
//
// If a hidden "summary" task is present we ignore it
//
if (NumberHelper.getInt(task.getUniqueID()) == 0)
{
continue;
}
//
// Select the actual or forecast start date. Note that the
// behaviour is different for milestones. The milestone end date
// is always correct, the milestone start date may be different
// to reflect a missed deadline.
//
LocalDateTime taskStartDate;
if (task.getMilestone())
{
taskStartDate = task.getActualFinish();
if (taskStartDate == null)
{
taskStartDate = task.getFinish();
}
}
else
{
taskStartDate = task.getActualStart();
if (taskStartDate == null)
{
taskStartDate = task.getStart();
}
}
if (taskStartDate != null)
{
if (startDate == null)
{
startDate = taskStartDate;
}
else
{
if (taskStartDate.isBefore(startDate))
{
startDate = taskStartDate;
}
}
}
}
return (startDate);
}
/**
* Find the latest task finish date.
*
* @return finish date
*/
public LocalDateTime getLatestFinishDate()
{
LocalDateTime finishDate = null;
for (Task task : m_tasks)
{
//
// If a hidden "summary" task is present we ignore it
//
if (NumberHelper.getInt(task.getUniqueID()) == 0)
{
continue;
}
//
// Select the actual or forecast start date
//
LocalDateTime taskFinishDate;
taskFinishDate = task.getActualFinish();
if (taskFinishDate == null)
{
taskFinishDate = task.getFinish();
}
if (taskFinishDate != null)
{
if (finishDate == null)
{
finishDate = taskFinishDate;
}
else
{
if (taskFinishDate.isAfter(finishDate))
{
finishDate = taskFinishDate;
}
}
}
}
return (finishDate);
}
/**
* This method returns a list of the views defined in this MPP file.
*
* @return list of views
*/
public ViewContainer getViews()
{
return m_views;
}
/**
* This method returns the tables defined in an MPP file.
*
* @return list of tables
*/
public TableContainer getTables()
{
return m_tables;
}
/**
* This method returns the filters defined in an MPP file.
*
* @return filters
*/
public FilterContainer getFilters()
{
return m_filters;
}
/**
* Retrieves a list of all groups.
*
* @return list of all groups
*/
public GroupContainer getGroups()
{
return m_groups;
}
/**
* Retrieve the event manager for this project.
*
* @return event manager
*/
public EventManager getEventManager()
{
return m_eventManager;
}
/**
* Retrieves the custom fields for this project.
*
* @return custom fields
*/
public CustomFieldContainer getCustomFields()
{
return m_shared.getCustomFields();
}
/**
* Retrieves the activity code configuration for this project.
*
* @return activity codes
*/
public ActivityCodeContainer getActivityCodes()
{
return m_shared.getActivityCodes();
}
/**
* Retrieves the project code configuration for this project.
*
* @return project codes
*/
public ProjectCodeContainer getProjectCodes()
{
return m_shared.getProjectCodes();
}
/**
* Retrieves the resource code configuration for this project.
*
* @return resource codes
*/
public ResourceCodeContainer getResourceCodes()
{
return m_shared.getResourceCodes();
}
/**
* Retrieves the role code configuration for this project.
*
* @return role codes
*/
public RoleCodeContainer getRoleCodes()
{
return m_shared.getRoleCodes();
}
/**
* Retrieves the resource assignment code configuration for this project.
*
* @return resource assignment codes
*/
public ResourceAssignmentCodeContainer getResourceAssignmentCodes()
{
return m_shared.getResourceAssignmentCodes();
}
/**
* Retrieves the shifts for this project.
*
* @return shifts
*/
public ShiftContainer getShifts()
{
return m_shared.getShifts();
}
/**
* Retrieves the shift periods for this project.
*
* @return shift periods
*/
public ShiftPeriodContainer getShiftPeriods()
{
return m_shared.getShiftPeriods();
}
/**
* Retrieves the data link configuration for this project.
*
* @return data links
*/
public DataLinkContainer getDataLinks()
{
return m_dataLinks;
}
/**
* Retrieves the expense categories available for this schedule.
*
* @return expense categories
*/
public ExpenseCategoryContainer getExpenseCategories()
{
return m_shared.getExpenseCategories();
}
/**
* Retrieves the cost accounts available for this schedule.
*
* @return cost accounts
*/
public CostAccountContainer getCostAccounts()
{
return m_shared.getCostAccounts();
}
/**
* Retrieves the user defined fields available for this schedule.
*
* @return user defined fields
*/
public UserDefinedFieldContainer getUserDefinedFields()
{
return m_shared.getUserDefinedFields();
}
/**
* Retrieves the work contours available for this schedule.
*
* @return work contours
*/
public WorkContourContainer getWorkContours()
{
return m_shared.getWorkContours();
}
/**
* Retrieves the notes topics available for this schedule.
*
* @return notes topics
*/
public NotesTopicContainer getNotesTopics()
{
return m_shared.getNotesTopics();
}
/**
* Retrieve the locations available for this schedule.
*
* @return locations
*/
public LocationContainer getLocations()
{
return m_shared.getLocations();
}
/**
* Retrieve the units of measure available for this schedule.
*
* @return units of measure
*/
public UnitOfMeasureContainer getUnitsOfMeasure()
{
return m_shared.getUnitsOfMeasure();
}
/**
* Retrieves the default calendar for this project based on the calendar name
* given in the project properties. If a calendar of this name cannot be found, then
* the first calendar listed for the project will be returned. If the
* project contains no calendars, then a default calendar is added.
*
* @return default projectCalendar instance
*/
public ProjectCalendar getDefaultCalendar()
{
return getProjectProperties().getDefaultCalendar();
}
/**
* Sets the default calendar for this project.
*
* @param calendar default calendar instance
*/
public void setDefaultCalendar(ProjectCalendar calendar)
{
if (calendar != null)
{
m_properties.setDefaultCalendar(calendar);
}
}
/**
* Retrieve the calendar used internally for timephased baseline calculation.
* All baseline timephased data is relative to this calendar.
* The calendar is created at the point the first baseline is taken and is
* a copy of the default calendar at that time.
*
* @return baseline calendar
*/
public ProjectCalendar getBaselineCalendar()
{
//
// Attempt to locate the calendar normally used by baselines
// If this isn't present, fall back to using the default
// project calendar.
//
ProjectCalendar result = getCalendarByName(m_properties.getBaselineCalendarName());
if (result == null)
{
result = getDefaultCalendar();
}
return result;
}
/**
* Retrieve the baselines linked to this project.
* The baseline at index zero is the default baseline,
* the values at the remaining indexes (1-10) are the
* numbered baselines. The list will contain null
* if a particular baseline has not been set.
*
* @return list of baselines
*/
public List getBaselines()
{
return Arrays.asList(m_baselines);
}
/**
* Store the supplied project as the default baseline, and use it to set the
* baseline cost, duration, finish, fixed cost accrual, fixed cost, start and
* work attributes for the tasks in the current project.
*
* @param baseline baseline project
*/
public void setBaseline(ProjectFile baseline)
{
setBaseline(baseline, 0);
}
/**
* Retrieve the default baseline project.
*
* @return ProjectFile instance or null
*/
public ProjectFile getBaseline()
{
return getBaseline(0);
}
/**
* Store the supplied project as baselineN, and use it to set the
* baselineN cost, duration, finish, fixed cost accrual, fixed cost, start and
* work attributes for the tasks in the current project.
* The index argument selects which of the 10 baselines to populate. Passing
* an index of 0 populates the default baseline.
*
* @param baseline baseline project
* @param index baseline to populate (0-10)
*/
public void setBaseline(ProjectFile baseline, int index)
{
if (index < 0 || index >= m_baselines.length)
{
throw new IllegalArgumentException(index + " is not a valid baseline index");
}
m_baselines[index] = baseline;
if (index == 0)
{
m_properties.setBaselineDate(baseline.getProjectProperties().getCreationDate());
}
else
{
m_properties.setBaselineDate(index, baseline.getProjectProperties().getCreationDate());
}
m_config.getBaselineStrategy().populateBaseline(this, baseline, index);
}
/**
* Retrieve baselineN from Baseline, Baseline1, Baseline2 ... Baseline10.
* Returns null if the specified baseline has not been set.
*
* @param index 0-10 representing Baseline, Baseline1, Baseline2 ... Baseline10
* @return ProjectFile instance or null
*/
public ProjectFile getBaseline(int index)
{
if (index < 0 || index >= m_baselines.length)
{
throw new IllegalArgumentException(index + " is not a valid baseline index");
}
return m_baselines[index];
}
/**
* Clear the default baseline for this project.
*/
public void clearBaseline()
{
clearBaseline(0);
}
/**
* Clear baselineN (1-10) for this project.
*
* @param index baseline index
*/
public void clearBaseline(int index)
{
m_config.getBaselineStrategy().clearBaseline(this, index);
}
/**
* Set a map of tasks in this project to tasks in baseline project.
* Populated when a baseline is added.
*
* @param index baseline index
* @param map map of current project tasks to baseline project tasks
*/
void setBaselineTaskMap(int index, Map map)
{
m_baselineTaskMap.put(Integer.valueOf(index), map);
}
/**
* Map of tasks in this project to tasks in a baseline project.
* Populated when a baseline is added.
*
* @param index baseline index
* @return map of current project tasks to baseline project tasks
*/
Map getBaselineTaskMap(int index)
{
return m_baselineTaskMap.getOrDefault(Integer.valueOf(index), Collections.emptyMap());
}
/**
* A convenience method used to retrieve a set of FieldType instances representing
* all populated fields in the project.
*
* @return set of all populated fields
*/
public Set getPopulatedFields()
{
return Stream.of(m_tasks.getPopulatedFields(), m_resources.getPopulatedFields(), m_assignments.getPopulatedFields(), m_properties.getPopulatedFields()).flatMap(Collection::stream).collect(Collectors.toSet());
}
/**
* Calling this method will recursively expand any subprojects
* in the current file and in turn any subprojects those files contain.
* The tasks from the subprojects will be attached
* to what was originally the subproject task. Assuming all subproject
* files can be located and loaded correctly, this will present
* a complete view of the project.
*
* Note that the current project and any subprojects are still independent
* projects, so while you can recursively descend through the hierarchy
* of tasks to visit all tasks from all files, the {@code ProjectFile.getTasks()}
* collection will still only contain the tasks from the original project,
* not all the subprojects.
*
* Passing {@code true} for the {@code replaceExternalTasks} flag will
* replace any predecessor or successor relationships with external tasks
* with new relationships which link to the original tasks. For each
* external task where this is successful, the external task itself will
* be removed as it is just a placeholder and is no longer required.
*
* @param replaceExternalTasks flag indicating if external tasks should be replaced
*/
public void expandSubprojects(boolean replaceExternalTasks)
{
getTasks().stream().map(Task::expandSubproject).filter(Objects::nonNull).forEach(p -> p.expandSubprojects(replaceExternalTasks));
if (replaceExternalTasks)
{
replaceExternalTasks();
}
}
/**
* Calling this method will replace any predecessors or successors which link to external tasks with new predecessors or successors
* which link to the correct tasks across projects. As the external task instance are just placeholders,
* these are now removed as they serve no further purpose.
*/
private void replaceExternalTasks()
{
List externalTasks = new ArrayList<>();
findExternalTasks(getChildTasks(), externalTasks);
Set replacedTasks = externalTasks.stream().map(t -> replaceRelations(t)).filter(t -> t != null).collect(Collectors.toSet());
removeExternalTasks(getChildTasks(), replacedTasks);
}
/**
* Replaces any predecessor or successor relations for this external task.
*
* @param externalTask external task to replace
* @return the external task if relations successfully replaced, or null if not replaced
*/
private Task replaceRelations(Task externalTask)
{
ProjectFile originalProjectFile = findProject(externalTask.getSubprojectFile());
if (originalProjectFile == null)
{
return null;
}
Task originalTask = findTask(originalProjectFile, externalTask);
if (originalTask == null)
{
return null;
}
replaceRelations(externalTask, originalTask);
return externalTask;
}
/**
* Given a project's filename, find the relevant ProjectFile instance.
*
* @param name project filename
* @return ProjectFile instance or null if the project can't be found
*/
private ProjectFile findProject(String name)
{
if (name.equals(m_properties.getProjectFilePath()))
{
return this;
}
return m_externalProjects.read(name);
}
/**
* Find the original task in a ProjectFile instance which is represented by
* an external task.
*
* @param file project containing the original task
* @param externalTask external task representing the original task
* @return Task instance, or null if we can't find the original task
*/
private Task findTask(ProjectFile file, Task externalTask)
{
Integer id = externalTask.getSubprojectTaskUniqueID();
if (id != null)
{
Task result = file.getTaskByUniqueID(id);
if (result != null)
{
return result;
}
}
id = externalTask.getSubprojectTaskID();
if (id != null)
{
return file.getTaskByID(id);
}
return null;
}
/**
* Where we have predecessor or successor Relation instances which link to external tasks,
* replace these with new Relation instance which link to the original task.
*
* @param externalTask external Task instance
* @param originalTask original Task instance
*/
private void replaceRelations(Task externalTask, Task originalTask)
{
RelationContainer relations = externalTask.getParentFile().getRelations();
// create copies to avoid concurrent modification
List successors = new ArrayList<>(relations.getRawSuccessors(externalTask));
List predecessors = new ArrayList<>(relations.getPredecessors(externalTask));
for (Relation originalRelation : successors)
{
relations.remove(originalRelation);
originalRelation.getSuccessorTask().addPredecessor(new Relation.Builder().from(originalRelation).predecessorTask(originalTask));
}
for (Relation originalRelation : predecessors)
{
relations.remove(originalRelation);
originalTask.addPredecessor(new Relation.Builder().from(originalRelation));
}
}
/**
* Recursively descend through the hierarchy of tasks to identify external tasks,
* and return them in the supplied list.
*
* @param tasks list of tasks to examine
* @param externalTasks list of external tasks
*/
private void findExternalTasks(List tasks, List externalTasks)
{
externalTasks.addAll(tasks.stream().filter(t -> t.getExternalTask()).collect(Collectors.toList()));
tasks.forEach(t -> findExternalTasks(t.getChildTasks(), externalTasks));
}
/**
* This method recursively descends through the hierarchy of tasks
* to remove any external tasks which are no longer required.
*
* @param tasks list of tasks to examine
* @param replacedTasks set of external tasks to remove
*/
private void removeExternalTasks(List tasks, Set replacedTasks)
{
tasks.removeIf(t -> replacedTasks.contains(t));
for (Task task : tasks)
{
removeExternalTasks(task.getChildTasks(), replacedTasks);
}
}
/**
* Called by a reader class when reading a schedule is complete.
*/
public void readComplete()
{
fixUniqueIdClashes();
}
/**
* This method is called to renumber any Unique ID values which
* were found to have duplicates.
*/
public void fixUniqueIdClashes()
{
getTasks().fixUniqueIdClashes();
getResources().fixUniqueIdClashes();
getCalendars().fixUniqueIdClashes();
getResourceAssignments().fixUniqueIdClashes();
getRelations().fixUniqueIdClashes();
}
/**
* Retrieve the ObjectSequence instance used to generate Unique ID values for a given class.
*
* @param c target class
* @return ObjectSequence instance
*/
@Override public ObjectSequence getUniqueIdObjectSequence(Class> c)
{
return ProjectFileSharedData.contains(c) ? m_shared.getUniqueIdObjectSequence(c) : m_uniqueIdObjectSequences.computeIfAbsent(c.getName(), x -> new ObjectSequence(1));
}
/**
* Add an error which has been ignored while reading this schedule.
*
* @param ex ignored error
*/
public void addIgnoredError(Exception ex)
{
m_ignoredErrors.add(ex);
}
/**
* Retrieve a list of errors ignored when reading this schedule.
*
* @return list of errors
*/
public List getIgnoredErrors()
{
return m_ignoredErrors;
}
void addExternalProject(String fileName, ProjectFile projectFile)
{
m_externalProjects.add(fileName, projectFile);
}
ProjectFile readExternalProject(String fileName)
{
return m_externalProjects.read(fileName);
}
private final ProjectConfig m_config = new ProjectConfig();
private final ProjectProperties m_properties = new ProjectProperties(this);
private final ResourceContainer m_resources = new ResourceContainer(this);
private final TaskContainer m_tasks = new TaskContainer(this);
private final List m_childTasks = new ArrayList<>();
private final List m_childResources = new ArrayList<>();
private final ResourceAssignmentContainer m_assignments = new ResourceAssignmentContainer(this);
private final RelationContainer m_relations = new RelationContainer(this);
private final ProjectCalendarContainer m_calendars = new ProjectCalendarContainer(this);
private final TableContainer m_tables = new TableContainer();
private final FilterContainer m_filters = new FilterContainer();
private final GroupContainer m_groups = new GroupContainer();
private final ViewContainer m_views = new ViewContainer();
private final EventManager m_eventManager = new EventManager();
private final DataLinkContainer m_dataLinks = new DataLinkContainer();
private final ExternalProjectContainer m_externalProjects = new ExternalProjectContainer(this);
private final ProjectFile[] m_baselines = new ProjectFile[11];
private final Map> m_baselineTaskMap = new HashMap<>();
private final List m_ignoredErrors = new ArrayList<>();
private final Map m_uniqueIdObjectSequences = new HashMap<>();
private final ProjectFileSharedData m_shared;
}
© 2015 - 2025 Weber Informatics LLC | Privacy Policy