/*
* file: MPPReader.java
* author: Jon Iles
* copyright: (c) Packwood Software 2005
* date: 2005-12-21
*/
/*
* 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.mpp;
import java.io.IOException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import net.sf.mpxj.DateRange;
import net.sf.mpxj.MPXJException;
import net.sf.mpxj.ProjectConfig;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.Relation;
import net.sf.mpxj.Task;
import net.sf.mpxj.listener.ProjectListener;
import net.sf.mpxj.reader.AbstractProjectReader;
import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;
import org.apache.poi.poifs.filesystem.POIFSFileSystem;
/**
* This class creates a new ProjectFile instance by reading an MPP file.
*/
public final class MPPReader extends AbstractProjectReader
{
/**
* {@inheritDoc}
*/
@Override public void addProjectListener(ProjectListener listener)
{
if (m_projectListeners == null)
{
m_projectListeners = new LinkedList<ProjectListener>();
}
m_projectListeners.add(listener);
}
/**
* {@inheritDoc}
*/
@Override public ProjectFile read(InputStream is) throws MPXJException
{
try
{
//
// Open the file system
//
POIFSFileSystem fs = new POIFSFileSystem(is);
return read(fs);
}
catch (IOException ex)
{
throw new MPXJException(MPXJException.READ_ERROR, ex);
}
}
/**
* Alternative entry point allowing an MPP file to be read from
* a user-supplied POI file stream.
*
* @param fs POI file stream
* @return ProjectFile instance
* @throws MPXJException
*/
public ProjectFile read(POIFSFileSystem fs) throws MPXJException
{
try
{
ProjectFile projectFile = new ProjectFile();
ProjectConfig config = projectFile.getProjectConfig();
config.setAutoTaskID(false);
config.setAutoTaskUniqueID(false);
config.setAutoResourceID(false);
config.setAutoResourceUniqueID(false);
config.setAutoOutlineLevel(false);
config.setAutoOutlineNumber(false);
config.setAutoWBS(false);
config.setAutoCalendarUniqueID(false);
config.setAutoAssignmentUniqueID(false);
projectFile.getEventManager().addProjectListeners(m_projectListeners);
//
// Open the file system and retrieve the root directory
//
DirectoryEntry root = fs.getRoot();
//
// Retrieve the CompObj data, validate the file format and process
//
CompObj compObj = new CompObj(new DocumentInputStream((DocumentEntry) root.getEntry("\1CompObj")));
projectFile.getProjectProperties().setFullApplicationName(compObj.getApplicationName());
projectFile.getProjectProperties().setApplicationVersion(compObj.getApplicationVersion());
String format = compObj.getFileFormat();
Class<? extends MPPVariantReader> readerClass = FILE_CLASS_MAP.get(format);
if (readerClass == null)
{
throw new MPXJException(MPXJException.INVALID_FILE + ": " + format);
}
MPPVariantReader reader = readerClass.newInstance();
reader.process(this, projectFile, root);
//
// Update the internal structure. We'll take this opportunity to
// generate outline numbers for the tasks as they don't appear to
// be present in the MPP file.
//
config.setAutoOutlineNumber(true);
projectFile.updateStructure();
config.setAutoOutlineNumber(false);
//
// Perform post-processing to set the summary flag and clean
// up any instances where a task has an empty splits list.
//
for (Task task : projectFile.getAllTasks())
{
task.setSummary(task.getChildTasks().size() != 0);
List<DateRange> splits = task.getSplits();
if (splits != null && splits.isEmpty())
{
task.setSplits(null);
}
validationRelations(task);
}
//
// Ensure that the unique ID counters are correct
//
config.updateUniqueCounters();
return (projectFile);
}
catch (IOException ex)
{
throw new MPXJException(MPXJException.READ_ERROR, ex);
}
catch (IllegalAccessException ex)
{
throw new MPXJException(MPXJException.READ_ERROR, ex);
}
catch (InstantiationException ex)
{
throw new MPXJException(MPXJException.READ_ERROR, ex);
}
}
/**
* This method validates all relationships for a task, removing
* any which have been incorrectly read from the MPP file and
* point to a parent task.
*
* @param task task under test
*/
private void validationRelations(Task task)
{
List<Relation> predecessors = task.getPredecessors();
if (predecessors != null)
{
ArrayList<Relation> invalid = new ArrayList<Relation>();
for (Relation relation : predecessors)
{
Task sourceTask = relation.getSourceTask();
Task targetTask = relation.getTargetTask();
String sourceOutlineNumber = sourceTask.getOutlineNumber();
String targetOutlineNumber = targetTask.getOutlineNumber();
if (sourceOutlineNumber != null && targetOutlineNumber != null && sourceOutlineNumber.startsWith(targetOutlineNumber + '.'))
{
invalid.add(relation);
}
}
for (Relation relation : invalid)
{
relation.getSourceTask().removePredecessor(relation.getTargetTask(), relation.getType(), relation.getLag());
}
}
}
/**
* This method retrieves the state of the preserve note formatting flag.
*
* @return boolean flag
*/
public boolean getPreserveNoteFormatting()
{
return (m_preserveNoteFormatting);
}
/**
* This method sets a flag to indicate whether the RTF formatting associated
* with notes should be preserved or removed. By default the formatting
* is removed.
*
* @param preserveNoteFormatting boolean flag
*/
public void setPreserveNoteFormatting(boolean preserveNoteFormatting)
{
m_preserveNoteFormatting = preserveNoteFormatting;
}
/**
* If this flag is true, raw timephased data will be retrieved
* from MS Project: no normalisation will take place.
*
* @return boolean flag
*/
public boolean getUseRawTimephasedData()
{
return m_useRawTimephasedData;
}
/**
* If this flag is true, raw timephased data will be retrieved
* from MS Project: no normalisation will take place.
*
* @param useRawTimephasedData boolean flag
*/
public void setUseRawTimephasedData(boolean useRawTimephasedData)
{
m_useRawTimephasedData = useRawTimephasedData;
}
/**
* Retrieves a flag which indicates whether presentation data will
* be read from the MPP file. Not reading this data saves time and memory.
*
* @return presentation data flag
*/
public boolean getReadPresentationData()
{
return m_readPresentationData;
}
/**
* Flag to allow time and memory to be saved by not reading
* presentation data from the MPP file.
*
* @param readPresentationData set to false to prevent presentation data being read
*/
public void setReadPresentationData(boolean readPresentationData)
{
m_readPresentationData = readPresentationData;
}
/**
* Flag to determine if the reader should only read the project properties.
* This allows for rapid access to the document properties, without the
* cost of reading the entire contents of the project file.
*
* @return true if the reader should only read the project properties
*/
public boolean getReadPropertiesOnly()
{
return m_readPropertiesOnly;
}
/**
* Flag to determine if the reader should only read the project properties.
* This allows for rapid access to the document properties, without the
* cost of reading the entire contents of the project file.
*
* @param readPropertiesOnly true if the reader should only read the project properties
*/
public void setReadPropertiesOnly(boolean readPropertiesOnly)
{
m_readPropertiesOnly = readPropertiesOnly;
}
/**
* Set the read password for this Project file. This is needed in order to
* be allowed to read a read-protected Project file.
*
* Note: Set this each time before calling the read method.
*
* @param password password text
*/
public void setReadPassword(String password)
{
m_readPassword = password;
}
/**
* Internal only. Get the read password for this Project file. This is
* needed in order to be allowed to read a read-protected Project file.
*
* @return password password text
*/
public String getReadPassword()
{
return m_readPassword;
}
/**
* Set the write password for this Project file. Currently not used.
*
* Note: Set this each time before calling the read method.
*
* @param password password text
*/
public void setWritePassword(String password)
{
m_writePassword = password;
}
/**
* Internal only. Get the write password for this Project file.
* Currently not used.
*
* @return password
*/
public String getWritePassword()
{
return m_writePassword;
}
/**
* Flag used to indicate whether RTF formatting in notes should
* be preserved. The default value for this flag is false.
*/
private boolean m_preserveNoteFormatting;
/**
* Setting this flag to true allows raw timephased data to be retrieved.
*/
private boolean m_useRawTimephasedData;
/**
* Flag to allow time and memory to be saved by not reading
* presentation data from the MPP file.
*/
private boolean m_readPresentationData = true;
private boolean m_readPropertiesOnly;
private String m_readPassword;
private String m_writePassword;
private List<ProjectListener> m_projectListeners;
/**
* Populate a map of file types and file processing classes.
*/
private static final Map<String, Class<? extends MPPVariantReader>> FILE_CLASS_MAP = new HashMap<String, Class<? extends MPPVariantReader>>();
static
{
FILE_CLASS_MAP.put("MSProject.MPP9", MPP9Reader.class);
FILE_CLASS_MAP.put("MSProject.MPT9", MPP9Reader.class);
FILE_CLASS_MAP.put("MSProject.GLOBAL9", MPP9Reader.class);
FILE_CLASS_MAP.put("MSProject.MPP8", MPP8Reader.class);
FILE_CLASS_MAP.put("MSProject.MPT8", MPP8Reader.class);
FILE_CLASS_MAP.put("MSProject.MPP12", MPP12Reader.class);
FILE_CLASS_MAP.put("MSProject.MPT12", MPP12Reader.class);
FILE_CLASS_MAP.put("MSProject.GLOBAL12", MPP12Reader.class);
FILE_CLASS_MAP.put("MSProject.MPP14", MPP14Reader.class);
FILE_CLASS_MAP.put("MSProject.MPT14", MPP14Reader.class);
FILE_CLASS_MAP.put("MSProject.GLOBAL14", MPP14Reader.class);
}
}