/*
 * file:       MPP9Reader.java
 * author:     Jon Iles
 * copyright:  (c) Packwood Software 2002-2006
 * date:       22/05/2003
 */

/*
 * 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.Collections;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.TreeMap;

import org.apache.poi.poifs.filesystem.DirectoryEntry;
import org.apache.poi.poifs.filesystem.DocumentEntry;
import org.apache.poi.poifs.filesystem.DocumentInputStream;

import net.sf.mpxj.CustomFieldContainer;
import net.sf.mpxj.Duration;
import net.sf.mpxj.EventManager;
import net.sf.mpxj.FieldType;
import net.sf.mpxj.MPXJException;
import net.sf.mpxj.ProjectCalendar;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.ProjectProperties;
import net.sf.mpxj.Resource;
import net.sf.mpxj.ResourceField;
import net.sf.mpxj.ResourceType;
import net.sf.mpxj.SubProject;
import net.sf.mpxj.Table;
import net.sf.mpxj.TableContainer;
import net.sf.mpxj.Task;
import net.sf.mpxj.TaskField;
import net.sf.mpxj.TimeUnit;
import net.sf.mpxj.View;
import net.sf.mpxj.common.DateHelper;
import net.sf.mpxj.common.MPPResourceField;
import net.sf.mpxj.common.MPPTaskField;
import net.sf.mpxj.common.NumberHelper;
import net.sf.mpxj.common.RtfHelper;

/**
 * This class is used to represent a Microsoft Project MPP9 file. This
 * implementation allows the file to be read, and the data it contains
 * exported as a set of MPX objects. These objects can be interrogated
 * to retrieve any required data, or stored as an MPX file.
 */
final class MPP9Reader implements MPPVariantReader
{
   /**
    * This method is used to process an MPP9 file. This is the file format
    * used by Project 2000, 2002, and 2003.
    *
    * @param reader parent file reader
    * @param file parent MPP file
    * @param root Root of the POI file system.
    */
   @Override public void process(MPPReader reader, ProjectFile file, DirectoryEntry root) throws MPXJException, IOException
   {
      try
      {
         populateMemberData(reader, file, root);
         processProjectProperties();

         if (!reader.getReadPropertiesOnly())
         {
            processSubProjectData();
            processGraphicalIndicators();
            processCustomValueLists();
            processCalendarData();
            processResourceData();
            processTaskData();
            processConstraintData();
            processAssignmentData();
            postProcessTasks();
            processDataLinks();

            if (reader.getReadPresentationData())
            {
               processViewPropertyData();
               processTableData();
               processViewData();
               processFilterData();
               processGroupData();
               processSavedViewState();
            }
         }
      }

      finally
      {
         clearMemberData();
      }
   }

   /**
    * Populate member data used by the rest of the reader.
    *
    * @param reader parent file reader
    * @param file parent MPP file
    * @param root Root of the POI file system.
    */
   private void populateMemberData(MPPReader reader, ProjectFile file, DirectoryEntry root) throws MPXJException, IOException
   {
      m_reader = reader;
      m_file = file;
      m_eventManager = file.getEventManager();
      m_root = root;

      //
      // Retrieve the high level document properties (never encoded)
      //
      Props9 props9 = new Props9(new DocumentInputStream(((DocumentEntry) root.getEntry("Props9"))));
      //System.out.println(props9);

      file.getProjectProperties().setProjectFilePath(props9.getUnicodeString(Props.PROJECT_FILE_PATH));
      m_inputStreamFactory = new DocumentInputStreamFactory(props9);

      //
      // Test for password protection. In the single byte retrieved here:
      //
      // 0x00 = no password
      // 0x01 = protection password has been supplied
      // 0x02 = write reservation password has been supplied
      // 0x03 = both passwords have been supplied
      byte passwordProtectionFlag = props9.getByte(Props.PASSWORD_FLAG);
      boolean passwordRequiredToRead = (passwordProtectionFlag & 0x1) != 0;
      //boolean passwordRequiredToWrite = (passwordProtectionFlag & 0x2) != 0;

      if (passwordRequiredToRead && reader.getRespectPasswordProtection())
      {
         // File is password protected for reading, let's read the password
         // and see if the correct read password was given to us.
         String readPassword = MPPUtility.decodePassword(props9.getByteArray(Props.PROTECTION_PASSWORD_HASH), m_inputStreamFactory.getEncryptionCode());
         // It looks like it is possible for a project file to have the password protection flag on without a password. In
         // this case MS Project treats the file as NOT protected. We need to do the same. It is worth noting that MS Project does
         // correct the problem if the file is re-saved (at least it did for me).
         if (readPassword != null && readPassword.length() > 0)
         {
            // See if the correct read password was given
            if (reader.getReadPassword() == null || reader.getReadPassword().matches(readPassword) == false)
            {
               // Passwords don't match
               throw new MPXJException(MPXJException.PASSWORD_PROTECTED_ENTER_PASSWORD);
            }
         }
         // Passwords matched so let's allow the reading to continue.
      }

      m_resourceMap = new HashMap<>();
      m_projectDir = (DirectoryEntry) root.getEntry("   19");
      m_viewDir = (DirectoryEntry) root.getEntry("   29");
      DirectoryEntry outlineCodeDir = (DirectoryEntry) m_projectDir.getEntry("TBkndOutlCode");
      VarMeta outlineCodeVarMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) outlineCodeDir.getEntry("VarMeta"))));
      m_outlineCodeVarData = new Var2Data(outlineCodeVarMeta, new DocumentInputStream(((DocumentEntry) outlineCodeDir.getEntry("Var2Data"))));
      m_projectProps = new Props9(m_inputStreamFactory.getInstance(m_projectDir, "Props"));
      //MPPUtility.fileDump("c:\\temp\\props.txt", m_projectProps.toString().getBytes());

      m_fontBases = new HashMap<>();
      m_taskSubProjects = new HashMap<>();
      m_taskOrder = new TreeMap<>();
      m_nullTaskOrder = new TreeMap<>();

      m_file.getProjectProperties().setMppFileType(Integer.valueOf(9));
      m_file.getProjectProperties().setAutoFilter(props9.getBoolean(Props.AUTO_FILTER));
   }

   /**
    * Clear transient member data.
    */
   private void clearMemberData()
   {
      m_reader = null;
      m_file = null;
      m_eventManager = null;
      m_root = null;
      m_resourceMap = null;
      m_projectDir = null;
      m_viewDir = null;
      m_outlineCodeVarData = null;
      m_fontBases = null;
      m_taskOrder = null;
      m_nullTaskOrder = null;
      m_taskSubProjects = null;
   }

   /**
    * Process the project properties data.
    */
   private void processProjectProperties() throws MPXJException
   {
      ProjectPropertiesReader reader = new ProjectPropertiesReader();
      reader.process(m_file, m_projectProps, m_root);
   }

   /**
    * Process the graphical indicator data.
    */
   private void processGraphicalIndicators()
   {
      GraphicalIndicatorReader graphicalIndicatorReader = new GraphicalIndicatorReader();
      graphicalIndicatorReader.process(m_file.getCustomFields(), m_file.getProjectProperties(), m_projectProps);
   }

   /**
    * Read sub project data from the file, and add it to a hash map
    * indexed by task ID.
    *
    * Project stores all subprojects that have ever been inserted into this project
    * in sequence and that is what used to count unique id offsets for each of the
    * subprojects.
    */
   private void processSubProjectData()
   {
      byte[] subProjData = m_projectProps.getByteArray(Props.SUBPROJECT_DATA);

      //System.out.println (ByteArrayHelper.hexdump(subProjData, true, 16, ""));
      //MPPUtility.fileHexDump("c:\\temp\\dump.txt", subProjData);

      if (subProjData != null)
      {
         int index = 0;
         int offset = 0;
         int itemHeaderOffset;
         int uniqueIDOffset;
         int filePathOffset;
         int fileNameOffset;
         SubProject sp;

         byte[] itemHeader = new byte[20];

         /*int blockSize = MPPUtility.getInt(subProjData, offset);*/
         offset += 4;

         /*int unknown = MPPUtility.getInt(subProjData, offset);*/
         offset += 4;

         int itemCountOffset = MPPUtility.getInt(subProjData, offset);
         offset += 4;

         while (offset < itemCountOffset)
         {
            index++;
            itemHeaderOffset = MPPUtility.getShort(subProjData, offset);
            offset += 4;

            MPPUtility.getByteArray(subProjData, itemHeaderOffset, itemHeader.length, itemHeader, 0);

            //            System.out.println ();
            //            System.out.println ();
            //            System.out.println ("offset=" + offset);
            //            System.out.println ("ItemHeaderOffset=" + itemHeaderOffset);
            //            System.out.println ("type=" + ByteArrayHelper.hexdump(itemHeader, 16, 1, false));
            //            System.out.println (ByteArrayHelper.hexdump(itemHeader, false, 16, ""));

            byte subProjectType = itemHeader[16];
            switch (subProjectType)
            {
               //
               // Subproject that is no longer inserted. This is a placeholder in order to be
               // able to always guarantee unique unique ids.
               //
               case 0x00:
               {
                  offset += 8;
                  break;
               }

               //
               // task unique ID, 8 bytes, path, file name
               //
               case (byte) 0x99:
               case 0x09:
               case 0x0D:
               {
                  uniqueIDOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  // sometimes offset of a task ID?
                  offset += 4;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  break;
               }

               //
               // task unique ID, 8 bytes, path, file name
               //
               case (byte) 0x91:
               {
                  uniqueIDOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  // Unknown offset
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  break;
               }

               //
               // task unique ID, path, file name
               //
               case 0x01:
               case 0x03:
               case 0x08:
               case 0x0A:
               case 0x11:
               {
                  uniqueIDOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  break;
               }

               //
               // task unique ID, path, unknown, file name
               //
               case (byte) 0x81:
               case 0x41:
               {
                  uniqueIDOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  // unknown offset to 2 bytes of data?
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  break;
               }

               //
               // task unique ID, path, file name
               //
               case (byte) 0xC0:
               {
                  uniqueIDOffset = itemHeaderOffset;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  // unknown offset
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  break;
               }

               //
               // resource, task unique ID, path, file name
               //
               case 0x05:
               {
                  uniqueIDOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  m_file.getSubProjects().setResourceSubProject(sp);
                  break;
               }

               case 0x45:
               {
                  uniqueIDOffset = MPPUtility.getInt(subProjData, offset) & 0x1FFFF;
                  offset += 4;

                  filePathOffset = MPPUtility.getInt(subProjData, offset) & 0x1FFFF;
                  offset += 4;

                  fileNameOffset = MPPUtility.getInt(subProjData, offset) & 0x1FFFF;
                  offset += 4;

                  offset += 4;
                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  m_file.getSubProjects().setResourceSubProject(sp);
                  break;
               }

               //
               // path, file name
               //
               case 0x02:
               case 0x04:
               {
                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, -1, filePathOffset, fileNameOffset, index);
                  // 0x02 looks to be the link FROM the resource pool to a project that uses it
                  if (subProjectType == 0x04)
                  {
                     m_file.getSubProjects().setResourceSubProject(sp);
                  }
                  break;
               }

               //
               // task unique ID, 4 bytes, path, 4 bytes, file name
               //
               case (byte) 0x8D:
               {
                  uniqueIDOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 8;

                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 8;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, uniqueIDOffset, filePathOffset, fileNameOffset, index);
                  break;
               }

               //
               // Appears when a subproject is collapsed
               //
               case (byte) 0x80:
               {
                  offset += 12;
                  break;
               }

               // deleted entry?
               case 0x10:
               {
                  offset += 8;
                  break;
               }

               // new resource pool entry
               case (byte) 0x44:
               {
                  filePathOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  offset += 4;

                  fileNameOffset = MPPUtility.getShort(subProjData, offset);
                  offset += 4;

                  sp = readSubProject(subProjData, -1, filePathOffset, fileNameOffset, index);
                  m_file.getSubProjects().setResourceSubProject(sp);
                  break;
               }

               //
               // Any other value, assume 12 bytes to handle old/deleted data?
               //
               default:
               {
                  offset += 12;
                  break;
               }
            }
         }
      }
   }

   /**
    * Method used to read the sub project details from a byte array.
    *
    * @param data byte array
    * @param uniqueIDOffset offset of unique ID
    * @param filePathOffset offset of file path
    * @param fileNameOffset offset of file name
    * @param subprojectIndex index of the subproject, used to calculate unique id offset
    * @return new SubProject instance
    */
   private SubProject readSubProject(byte[] data, int uniqueIDOffset, int filePathOffset, int fileNameOffset, int subprojectIndex)
   {
      try
      {
         SubProject sp = new SubProject();

         if (uniqueIDOffset != -1)
         {
            int prev = 0;
            int value = MPPUtility.getInt(data, uniqueIDOffset);
            while (value != SUBPROJECT_LISTEND)
            {
               switch (value)
               {
                  case SUBPROJECT_TASKUNIQUEID0:
                  case SUBPROJECT_TASKUNIQUEID1:
                  case SUBPROJECT_TASKUNIQUEID2:
                  case SUBPROJECT_TASKUNIQUEID3:
                  case SUBPROJECT_TASKUNIQUEID4:
                  case SUBPROJECT_TASKUNIQUEID5:
                     // The previous value was for the subproject unique task id
                     sp.setTaskUniqueID(Integer.valueOf(prev));
                     m_taskSubProjects.put(sp.getTaskUniqueID(), sp);
                     prev = 0;
                     break;

                  default:
                     if (prev != 0)
                     {
                        // The previous value was for an external task unique task id
                        sp.addExternalTaskUniqueID(Integer.valueOf(prev));
                        m_taskSubProjects.put(Integer.valueOf(prev), sp);
                     }
                     prev = value;
                     break;
               }
               // Read the next value
               uniqueIDOffset += 4;
               value = MPPUtility.getInt(data, uniqueIDOffset);
            }
            if (prev != 0)
            {
               // The previous value was for an external task unique task id
               sp.addExternalTaskUniqueID(Integer.valueOf(prev));
               m_taskSubProjects.put(Integer.valueOf(prev), sp);
            }

            // Now get the unique id offset for this subproject
            value = 0x00800000 + ((subprojectIndex - 1) * 0x00400000);
            sp.setUniqueIDOffset(Integer.valueOf(value));
         }

         //
         // First block header
         //
         filePathOffset += 18;

         //
         // String size as a 4 byte int
         //
         filePathOffset += 4;

         //
         // Full DOS path
         //
         sp.setDosFullPath(MPPUtility.getString(data, filePathOffset));
         filePathOffset += (sp.getDosFullPath().length() + 1);

         //
         // 24 byte block
         //
         filePathOffset += 24;

         //
         // 4 byte block size
         //
         int size = MPPUtility.getInt(data, filePathOffset);
         filePathOffset += 4;
         if (size == 0)
         {
            sp.setFullPath(sp.getDosFullPath());
         }
         else
         {
            //
            // 4 byte unicode string size in bytes
            //
            size = MPPUtility.getInt(data, filePathOffset);
            filePathOffset += 4;

            //
            // 2 byte data
            //
            filePathOffset += 2;

            //
            // Unicode string
            //
            sp.setFullPath(MPPUtility.getUnicodeString(data, filePathOffset, size));
            // filePathOffset += size;
         }

         //
         // Second block header
         //
         fileNameOffset += 18;

         //
         // String size as a 4 byte int
         //
         fileNameOffset += 4;

         //
         // DOS file name
         //
         sp.setDosFileName(MPPUtility.getString(data, fileNameOffset));
         fileNameOffset += (sp.getDosFileName().length() + 1);

         //
         // 24 byte block
         //
         fileNameOffset += 24;

         //
         // 4 byte block size
         //
         size = MPPUtility.getInt(data, fileNameOffset);
         fileNameOffset += 4;

         if (size == 0)
         {
            sp.setFileName(sp.getDosFileName());
         }
         else
         {
            //
            // 4 byte unicode string size in bytes
            //
            size = MPPUtility.getInt(data, fileNameOffset);
            fileNameOffset += 4;

            //
            // 2 byte data
            //
            fileNameOffset += 2;

            //
            // Unicode string
            //
            sp.setFileName(MPPUtility.getUnicodeString(data, fileNameOffset, size));
            //fileNameOffset += size;
         }

         //System.out.println(sp.toString());

         // Add to the list of subprojects
         m_file.getSubProjects().add(sp);

         return (sp);
      }

      //
      // Admit defeat at this point - we have probably stumbled
      // upon a data format we don't understand, so we'll fail
      // gracefully here. This will now be reported as a missing
      // sub project error by end users of the library, rather
      // than as an exception being thrown.
      //
      catch (ArrayIndexOutOfBoundsException ex)
      {
         return (null);
      }
   }

   /**
    * This method process the data held in the props file specific to the
    * visual appearance of the project data.
    */
   private void processViewPropertyData() throws IOException
   {
      Props9 props = new Props9(m_inputStreamFactory.getInstance(m_viewDir, "Props"));
      byte[] data = props.getByteArray(Props.FONT_BASES);
      if (data != null)
      {
         processBaseFonts(data);
      }

      ProjectProperties properties = m_file.getProjectProperties();
      properties.setShowProjectSummaryTask(props.getBoolean(Props.SHOW_PROJECT_SUMMARY_TASK));
   }

   /**
    * Create an index of base font numbers and their associated base
    * font instances.
    * @param data property data
    */
   private void processBaseFonts(byte[] data)
   {
      int offset = 0;

      int blockCount = MPPUtility.getShort(data, 0);
      offset += 2;

      int size;
      String name;

      for (int loop = 0; loop < blockCount; loop++)
      {
         /*unknownAttribute = MPPUtility.getShort(data, offset);*/
         offset += 2;

         size = MPPUtility.getShort(data, offset);
         offset += 2;

         name = MPPUtility.getUnicodeString(data, offset);
         offset += 64;

         if (name.length() != 0)
         {
            FontBase fontBase = new FontBase(Integer.valueOf(loop), name, size);
            m_fontBases.put(fontBase.getIndex(), fontBase);
         }
      }
   }

   /**
    * Retrieve any task field value lists defined in the MPP file.
    */
   private void processCustomValueLists() throws IOException
   {
      CustomFieldValueReader9 reader = new CustomFieldValueReader9(m_projectDir, m_file.getProjectProperties(), m_projectProps, m_file.getCustomFields());
      reader.process();
   }

   /**
    * Retrieves the description value list associated with a custom task field.
    * This method will return null if no descriptions for the value list has
    * been defined for this field.
    *
    * @param data data block
    * @return list of descriptions
    */
   public List<String> getTaskFieldDescriptions(byte[] data)
   {
      if (data == null || data.length == 0)
      {
         return null;
      }
      List<String> descriptions = new ArrayList<>();
      int offset = 0;
      while (offset < data.length)
      {
         String description = MPPUtility.getUnicodeString(data, offset);
         descriptions.add(description);
         offset += description.length() * 2 + 2;
      }
      return descriptions;
   }

   /**
    * Retrieves the description value list associated with a custom task field.
    * This method will return null if no descriptions for the value list has
    * been defined for this field.
    *
    * @param properties project properties
    * @param field task field
    * @param data data block
    * @return list of task field values
    */
   public List<Object> getTaskFieldValues(ProjectProperties properties, FieldType field, byte[] data)
   {
      if (field == null || data == null || data.length == 0)
      {
         return null;
      }

      List<Object> list = new ArrayList<>();
      int offset = 0;

      switch (field.getDataType())
      {
         case DATE:
            while (offset + 4 <= data.length)
            {
               Date date = MPPUtility.getTimestamp(data, offset);
               list.add(date);
               offset += 4;
            }
            break;
         case CURRENCY:
            while (offset + 8 <= data.length)
            {
               Double number = NumberHelper.getDouble(MPPUtility.getDouble(data, offset) / 100.0);
               list.add(number);
               offset += 8;
            }
            break;
         case NUMERIC:
            while (offset + 8 <= data.length)
            {
               Double number = NumberHelper.getDouble(MPPUtility.getDouble(data, offset));
               list.add(number);
               offset += 8;
            }
            break;
         case DURATION:
            while (offset + 6 <= data.length)
            {
               Duration duration = MPPUtility.getAdjustedDuration(properties, MPPUtility.getInt(data, offset), MPPUtility.getDurationTimeUnits(MPPUtility.getShort(data, offset + 4)));
               list.add(duration);
               offset += 6;
            }
            break;
         case STRING:
            while (offset < data.length)
            {
               String s = MPPUtility.getUnicodeString(data, offset);
               list.add(s);
               offset += s.length() * 2 + 2;
            }
            break;
         case BOOLEAN:
            while (offset + 2 <= data.length)
            {
               boolean b = (MPPUtility.getShort(data, offset) == 0x01);
               list.add(Boolean.valueOf(b));
               offset += 2;
            }
            break;
         default:
            return null;
      }

      return list;
   }

   /**
    * Retrieve any resource field aliases defined in the MPP file.
    *
    * @param map index to field map
    * @param data resource field name alias data
    */
   private void processFieldNameAliases(Map<Integer, FieldType> map, byte[] data)
   {
      if (data != null)
      {
         int offset = 0;
         int index = 0;
         CustomFieldContainer fields = m_file.getCustomFields();
         while (offset < data.length)
         {
            String alias = MPPUtility.getUnicodeString(data, offset);
            if (!alias.isEmpty())
            {
               FieldType field = map.get(Integer.valueOf(index));
               if (field != null)
               {
                  fields.getCustomField(field).setAlias(alias);
               }
            }
            offset += (alias.length() + 1) * 2;
            index++;
         }
      }
   }

   /**
    * This method maps the task unique identifiers to their index number
    * within the FixedData block.
    *
    * @param fieldMap field map
    * @param taskFixedMeta Fixed meta data for this task
    * @param taskFixedData Fixed data for this task
    * @param taskVarData Variable task data
    * @return Mapping between task identifiers and block position
    */
   private TreeMap<Integer, Integer> createTaskMap(FieldMap fieldMap, FixedMeta taskFixedMeta, FixedData taskFixedData, Var2Data taskVarData)
   {
      TreeMap<Integer, Integer> taskMap = new TreeMap<>();
      int uniqueIdOffset = fieldMap.getFixedDataOffset(TaskField.UNIQUE_ID);
      Integer taskNameKey = fieldMap.getVarDataKey(TaskField.NAME);
      int itemCount = taskFixedMeta.getAdjustedItemCount();
      int uniqueID;
      Integer key;

      //
      // First three items are not tasks, so let's skip them.
      // Note we're working backwards: where we have duplicate tasks the later ones
      // appear to be the correct versions (https://github.com/joniles/mpxj/issues/152)
      //
      for (int loop = itemCount - 1; loop > 2; loop--)
      {
         byte[] data = taskFixedData.getByteArrayValue(loop);
         if (data != null)
         {
            byte[] metaData = taskFixedMeta.getByteArrayValue(loop);

            //
            // Check for the deleted task flag
            //
            int flags = MPPUtility.getInt(metaData, 0);
            if ((flags & 0x02) != 0)
            {
               // Project stores the deleted tasks unique id's into the fixed data as well
               // and at least in one case the deleted task was listed twice in the list
               // the second time with data with it causing a phantom task to be shown.
               // See CalendarErrorPhantomTasks.mpp
               //
               // So let's add the unique id for the deleted task into the map so we don't
               // accidentally include the task later.
               //
               uniqueID = MPPUtility.getShort(data, TASK_UNIQUE_ID_FIXED_OFFSET); // Only a short stored for deleted tasks?
               key = Integer.valueOf(uniqueID);
               taskMap.put(key, null); // use null so we can easily ignore this later
            }
            else
            {
               //
               // Do we have a null task?
               //
               if (data.length == NULL_TASK_BLOCK_SIZE)
               {
                  uniqueID = MPPUtility.getInt(data, TASK_UNIQUE_ID_FIXED_OFFSET);
                  key = Integer.valueOf(uniqueID);
                  if (taskMap.containsKey(key) == false)
                  {
                     taskMap.put(key, Integer.valueOf(loop));
                  }
               }
               else
               {
                  //
                  // We apply a heuristic here - if we have more than 75% of the data, we assume
                  // the task is valid.
                  //
                  int maxSize = fieldMap.getMaxFixedDataSize(0);
                  if (maxSize == 0 || ((data.length * 100) / maxSize) > 75)
                  {
                     uniqueID = MPPUtility.getInt(data, uniqueIdOffset);
                     key = Integer.valueOf(uniqueID);

                     // Accept this task if it does not have a deleted unique ID or it has a deleted unique ID but the name is not null
                     if (!taskMap.containsKey(key) || taskVarData.getUnicodeString(key, taskNameKey) != null)
                     {
                        taskMap.put(key, Integer.valueOf(loop));
                     }
                  }
               }
            }
         }
      }

      return (taskMap);
   }

   /**
    * This method maps the resource unique identifiers to their index number
    * within the FixedData block.
    *
    * @param fieldMap field map
    * @param rscFixedMeta resource fixed meta data
    * @param rscFixedData resource fixed data
    * @return map of resource IDs to resource data
    */
   private TreeMap<Integer, Integer> createResourceMap(FieldMap fieldMap, FixedMeta rscFixedMeta, FixedData rscFixedData)
   {
      TreeMap<Integer, Integer> resourceMap = new TreeMap<>();
      int itemCount = rscFixedMeta.getAdjustedItemCount();

      for (int loop = 0; loop < itemCount; loop++)
      {
         byte[] data = rscFixedData.getByteArrayValue(loop);
         if (data == null || data.length < fieldMap.getMaxFixedDataSize(0))
         {
            continue;
         }

         Integer uniqueID = Integer.valueOf(MPPUtility.getShort(data, 0));
         resourceMap.put(uniqueID, Integer.valueOf(loop));
      }

      return (resourceMap);
   }

   /**
    * The format of the calendar data is a 4 byte header followed
    * by 7x 60 byte blocks, one for each day of the week. Optionally
    * following this is a set of 64 byte blocks representing exceptions
    * to the calendar.
    */
   private void processCalendarData() throws IOException
   {
      CalendarFactory factory = new MPP9CalendarFactory(m_file);
      factory.processCalendarData(m_projectDir, m_projectProps, m_inputStreamFactory, m_resourceMap);
   }

   /**
    * This method extracts and collates task data. The code below
    * goes through the modifier methods of the Task class in alphabetical
    * order extracting the data from the MPP file. Where there is no
    * mapping (e.g. the field is calculated on the fly, or we can't
    * find it in the data) the line is commented out.
    *
    * The missing boolean attributes are probably represented in the Props
    * section of the task data, which we have yet to decode.
    *
    * @throws IOException
    */
   private void processTaskData() throws IOException
   {
      FieldMap fieldMap = new FieldMap9(m_file.getProjectProperties(), m_file.getCustomFields());
      fieldMap.createTaskFieldMap(m_projectProps);

      DirectoryEntry taskDir = (DirectoryEntry) m_projectDir.getEntry("TBkndTask");
      VarMeta taskVarMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) taskDir.getEntry("VarMeta"))));
      Var2Data taskVarData = new Var2Data(taskVarMeta, new DocumentInputStream(((DocumentEntry) taskDir.getEntry("Var2Data"))));
      FixedMeta taskFixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) taskDir.getEntry("FixedMeta"))), 47);
      FixedData taskFixedData = new FixedData(taskFixedMeta, m_inputStreamFactory.getInstance(taskDir, "FixedData"), 768, fieldMap.getMaxFixedDataSize(0));
      //System.out.println(taskFixedData);
      //System.out.println(taskFixedMeta);
      //System.out.println(taskVarMeta);
      //System.out.println(taskVarData);

      processFieldNameAliases(TASK_FIELD_ALIASES, m_projectProps.getByteArray(Props.TASK_FIELD_NAME_ALIASES));

      TreeMap<Integer, Integer> taskMap = createTaskMap(fieldMap, taskFixedMeta, taskFixedData, taskVarData);
      // The var data may not contain all the tasks as tasks with no var data assigned will
      // not be saved in there. Most notably these are tasks with no name. So use the task map
      // which contains all the tasks.
      Object[] uniqueIdArray = taskMap.keySet().toArray(); //taskVarMeta.getUniqueIdentifierArray();
      Integer offset;
      byte[] data;
      byte[] metaData;
      Task task;
      boolean autoWBS = true;
      List<Task> externalTasks = new ArrayList<>();
      RecurringTaskReader recurringTaskReader = null;
      String notes;

      for (int loop = 0; loop < uniqueIdArray.length; loop++)
      {
         Integer uniqueID = (Integer) uniqueIdArray[loop];

         offset = taskMap.get(uniqueID);
         if (taskFixedData.isValidOffset(offset) == false)
         {
            continue;
         }

         data = taskFixedData.getByteArrayValue(offset.intValue());
         Integer id = Integer.valueOf(MPPUtility.getInt(data, fieldMap.getFixedDataOffset(TaskField.ID)));
         if (data.length == NULL_TASK_BLOCK_SIZE)
         {
            task = m_file.addTask();
            task.setNull(true);
            task.setUniqueID(Integer.valueOf(MPPUtility.getShort(data, TASK_UNIQUE_ID_FIXED_OFFSET)));
            task.setID(Integer.valueOf(MPPUtility.getShort(data, TASK_ID_FIXED_OFFSET)));
            m_nullTaskOrder.put(task.getID(), task.getUniqueID());
            continue;
         }

         if (data.length < fieldMap.getMaxFixedDataSize(0))
         {
            continue;
         }

         if (uniqueID.intValue() != 0 && !taskVarMeta.containsKey(uniqueID))
         {
            continue;
         }

         metaData = taskFixedMeta.getByteArrayValue(offset.intValue());
         //System.out.println (ByteArrayHelper.hexdump(data, false, 16, ""));
         //System.out.println (ByteArrayHelper.hexdump(metaData, 8, 4, false));
         //MPPUtility.dataDump(data, true, true, true, true, true, true, true);
         //MPPUtility.dataDump(metaData, true, true, true, true, true, true, true);
         //MPPUtility.varDataDump(taskVarData, id, true, true, true, true, true, true);
         byte[] recurringData = taskVarData.getByteArray(uniqueID, fieldMap.getVarDataKey(TaskField.RECURRING_DATA));

         Task temp = m_file.getTaskByID(id);
         if (temp != null)
         {
            // Task with this id already exists... determine if this is the 'real' task by seeing
            // if this task has some var data. This is sort of hokey, but it's the best method i have
            // been able to see.
            if (!taskVarMeta.getUniqueIdentifierSet().contains(uniqueID))
            {
               // Sometimes Project contains phantom tasks that coexist on the same id as a valid
               // task. In this case don't want to include the phantom task. Seems to be a very rare case.
               continue;
            }
            else
               if (temp.getName() == null)
               {
                  // Ok, this looks valid. Remove the previous instance since it is most likely not a valid task.
                  // At worst case this removes a task with an empty name.
                  m_file.removeTask(temp);
               }
         }

         task = m_file.addTask();

         task.disableEvents();
         fieldMap.populateContainer(TaskField.class, task, uniqueID, new byte[][]
         {
            data
         }, taskVarData);
         task.enableEvents();

         task.setEffortDriven((metaData[11] & 0x10) != 0);

         task.setEstimated(getDurationEstimated(MPPUtility.getShort(data, fieldMap.getFixedDataOffset(TaskField.ACTUAL_DURATION_UNITS))));

         task.setExpanded(((metaData[12] & 0x02) == 0));
         Integer externalTaskID = task.getSubprojectTaskID();
         if (externalTaskID != null && externalTaskID.intValue() != 0)
         {
            task.setSubprojectTaskID(externalTaskID);
            task.setExternalTask(true);
            externalTasks.add(task);
         }

         task.setFlag(1, (metaData[37] & 0x20) != 0);
         task.setFlag(2, (metaData[37] & 0x40) != 0);
         task.setFlag(3, (metaData[37] & 0x80) != 0);
         task.setFlag(4, (metaData[38] & 0x01) != 0);
         task.setFlag(5, (metaData[38] & 0x02) != 0);
         task.setFlag(6, (metaData[38] & 0x04) != 0);
         task.setFlag(7, (metaData[38] & 0x08) != 0);
         task.setFlag(8, (metaData[38] & 0x10) != 0);
         task.setFlag(9, (metaData[38] & 0x20) != 0);
         task.setFlag(10, (metaData[38] & 0x40) != 0);
         task.setFlag(11, (metaData[38] & 0x80) != 0);
         task.setFlag(12, (metaData[39] & 0x01) != 0);
         task.setFlag(13, (metaData[39] & 0x02) != 0);
         task.setFlag(14, (metaData[39] & 0x04) != 0);
         task.setFlag(15, (metaData[39] & 0x08) != 0);
         task.setFlag(16, (metaData[39] & 0x10) != 0);
         task.setFlag(17, (metaData[39] & 0x20) != 0);
         task.setFlag(18, (metaData[39] & 0x40) != 0);
         task.setFlag(19, (metaData[39] & 0x80) != 0);
         task.setFlag(20, (metaData[40] & 0x01) != 0);
         task.setHideBar((metaData[10] & 0x80) != 0);
         processHyperlinkData(task, taskVarData.getByteArray(uniqueID, fieldMap.getVarDataKey(TaskField.HYPERLINK_DATA)));

         task.setID(id);
         task.setIgnoreResourceCalendar(((metaData[10] & 0x02) != 0));
         task.setLevelAssignments((metaData[13] & 0x04) != 0);
         task.setLevelingCanSplit((metaData[13] & 0x02) != 0);
         task.setMarked((metaData[9] & 0x40) != 0);
         task.setMilestone((metaData[8] & 0x20) != 0);

         task.setOutlineCode(1, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE1_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(2, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE2_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(3, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE3_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(4, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE4_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(5, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE5_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(6, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE6_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(7, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE7_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(8, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE8_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(9, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE9_INDEX), OUTLINECODE_DATA));
         task.setOutlineCode(10, m_outlineCodeVarData.getUnicodeString((Integer) task.getCachedValue(TaskField.OUTLINE_CODE10_INDEX), OUTLINECODE_DATA));

         task.setRollup((metaData[10] & 0x08) != 0);
         task.setUniqueID(uniqueID);

         switch (task.getConstraintType())
         {
            //
            // Adjust the start and finish dates if the task
            // is constrained to start as late as possible.
            //
            case AS_LATE_AS_POSSIBLE:
            {
               if (DateHelper.compare(task.getStart(), task.getLateStart()) < 0)
               {
                  task.setStart(task.getLateStart());
               }
               if (DateHelper.compare(task.getFinish(), task.getLateFinish()) < 0)
               {
                  task.setFinish(task.getLateFinish());
               }
               break;
            }

            case START_NO_LATER_THAN:
            case FINISH_NO_LATER_THAN:
            {
               if (DateHelper.compare(task.getFinish(), task.getStart()) < 0)
               {
                  task.setFinish(task.getLateFinish());
               }
               break;
            }

            default:
            {
               break;
            }
         }

         //
         // Retrieve task recurring data
         //
         if (recurringData != null)
         {
            if (recurringTaskReader == null)
            {
               recurringTaskReader = new RecurringTaskReader(m_file.getProjectProperties());
            }
            recurringTaskReader.processRecurringTask(task, recurringData);
            task.setRecurring(true);
         }

         //
         // Retrieve the task notes.
         //
         //notes = taskVarData.getString(id, TASK_NOTES);
         notes = task.getNotes();
         if (!m_reader.getPreserveNoteFormatting())
         {
            notes = RtfHelper.strip(notes);
         }
         task.setNotes(notes);

         //
         // Set the calendar name
         //
         Integer calendarID = (Integer) task.getCachedValue(TaskField.CALENDAR_UNIQUE_ID);
         if (calendarID != null && calendarID.intValue() != -1)
         {
            ProjectCalendar calendar = m_file.getCalendarByUniqueID(calendarID);
            if (calendar != null)
            {
               task.setCalendar(calendar);
            }
         }

         //
         // Set the sub project flag
         //
         SubProject sp = m_taskSubProjects.get(task.getUniqueID());
         task.setSubProject(sp);

         //
         // Set the external flag
         //
         if (sp != null)
         {
            task.setExternalTask(sp.isExternalTask(task.getUniqueID()));
            if (task.getExternalTask())
            {
               task.setExternalTaskProject(sp.getFullPath());
            }
         }

         //
         // If we have a WBS value from the MPP file, don't autogenerate
         //
         if (task.getWBS() != null)
         {
            autoWBS = false;
         }

         //
         // Process any enterprise columns
         //
         processTaskEnterpriseColumns(fieldMap, task, taskVarData);

         //
         // Unfortunately it looks like 'null' tasks sometimes make it through,
         // so let's check for to see if we need to mark this task as a null
         // task after all.
         //
         if (task.getName() == null && ((task.getStart() == null || task.getStart().getTime() == MPPUtility.getEpochDate().getTime()) || (task.getFinish() == null || task.getFinish().getTime() == MPPUtility.getEpochDate().getTime()) || (task.getCreateDate() == null || task.getCreateDate().getTime() == MPPUtility.getEpochDate().getTime())))
         {
            m_file.removeTask(task);
            task = m_file.addTask();
            task.setNull(true);
            task.setUniqueID(uniqueID);
            task.setID(id);
            m_nullTaskOrder.put(task.getID(), task.getUniqueID());
            continue;
         }

         m_taskOrder.put(task.getID(), task.getUniqueID());

         //
         // Fire the task read event
         //
         m_eventManager.fireTaskReadEvent(task);
         //System.out.println(task);
         //dumpUnknownData (task.getName(), UNKNOWN_TASK_DATA, data);
      }

      //
      // Enable auto WBS if necessary
      //
      m_file.getProjectConfig().setAutoWBS(autoWBS);

      //
      // We have now read all of the tasks, so we are in a position
      // to perform post-processing to set up the relevant details
      // for each external task.
      //
      if (!externalTasks.isEmpty())
      {
         processExternalTasks(externalTasks);
      }
   }

   /**
    * Extracts task enterprise column values.
    *
    * @param fieldMap fieldMap
    * @param task task instance
    * @param taskVarData task var data
    */
   private void processTaskEnterpriseColumns(FieldMap fieldMap, Task task, Var2Data taskVarData)
   {
      byte[] data = null;
      Integer varDataKey = fieldMap.getVarDataKey(TaskField.ENTERPRISE_DATA);

      if (varDataKey != null)
      {
         data = taskVarData.getByteArray(task.getUniqueID(), varDataKey);
      }

      if (data != null)
      {
         PropsBlock props = new PropsBlock(data);
         //System.out.println(props);

         for (Integer key : props.keySet())
         {
            int keyValue = key.intValue() - MPPTaskField.TASK_FIELD_BASE;
            TaskField field = MPPTaskField.getInstance(keyValue);

            if (field != null)
            {
               Object value = null;

               switch (field.getDataType())
               {
                  case CURRENCY:
                  {
                     value = Double.valueOf(props.getDouble(key) / 100);
                     break;
                  }

                  case DATE:
                  {
                     value = props.getTimestamp(key);
                     break;
                  }

                  case WORK:
                  {
                     double durationValueInHours = MPPUtility.getDouble(props.getByteArray(key), 0) / 60000;
                     value = Duration.getInstance(durationValueInHours, TimeUnit.HOURS);
                     break;
                  }

                  case DURATION:
                  {
                     byte[] durationData = props.getByteArray(key);
                     double durationValueInHours = ((double) MPPUtility.getInt(durationData, 0)) / 600;
                     TimeUnit durationUnits;
                     if (durationData.length < 6)
                     {
                        durationUnits = TimeUnit.DAYS;
                     }
                     else
                     {
                        durationUnits = MPPUtility.getDurationTimeUnits(MPPUtility.getShort(durationData, 4));
                     }
                     Duration duration = Duration.getInstance(durationValueInHours, TimeUnit.HOURS);
                     value = duration.convertUnits(durationUnits, m_file.getProjectProperties());
                     break;
                  }

                  case BOOLEAN:
                  {
                     field = null;
                     int bits = props.getInt(key);
                     task.set(TaskField.ENTERPRISE_FLAG1, Boolean.valueOf((bits & 0x00002) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG2, Boolean.valueOf((bits & 0x00004) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG3, Boolean.valueOf((bits & 0x00008) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG4, Boolean.valueOf((bits & 0x00010) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG5, Boolean.valueOf((bits & 0x00020) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG6, Boolean.valueOf((bits & 0x00040) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG7, Boolean.valueOf((bits & 0x00080) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG8, Boolean.valueOf((bits & 0x00100) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG9, Boolean.valueOf((bits & 0x00200) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG10, Boolean.valueOf((bits & 0x00400) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG11, Boolean.valueOf((bits & 0x00800) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG12, Boolean.valueOf((bits & 0x01000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG13, Boolean.valueOf((bits & 0x02000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG14, Boolean.valueOf((bits & 0x04000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG15, Boolean.valueOf((bits & 0x08000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG16, Boolean.valueOf((bits & 0x10000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG17, Boolean.valueOf((bits & 0x20000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG18, Boolean.valueOf((bits & 0x40000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG19, Boolean.valueOf((bits & 0x80000) != 0));
                     task.set(TaskField.ENTERPRISE_FLAG20, Boolean.valueOf((bits & 0x100000) != 0));
                     break;
                  }

                  case NUMERIC:
                  {
                     value = Double.valueOf(props.getDouble(key));
                     break;
                  }

                  case STRING:
                  {
                     value = props.getUnicodeString(key);
                     break;
                  }

                  case PERCENTAGE:
                  {
                     value = Integer.valueOf(props.getShort(key));
                     break;
                  }

                  default:
                  {
                     break;
                  }
               }

               task.set(field, value);
            }
         }
      }
   }

   /**
    * Extracts resource enterprise column data.
    *
    * @param fieldMap field map
    * @param resource resource instance
    * @param resourceVarData resource var data
    */
   private void processResourceEnterpriseColumns(FieldMap fieldMap, Resource resource, Var2Data resourceVarData)
   {
      byte[] data = null;
      Integer varDataKey = fieldMap.getVarDataKey(ResourceField.ENTERPRISE_DATA);
      if (varDataKey != null)
      {
         data = resourceVarData.getByteArray(resource.getUniqueID(), varDataKey);
      }

      if (data != null)
      {
         PropsBlock props = new PropsBlock(data);
         //System.out.println(props);
         resource.setCreationDate(props.getTimestamp(Props.RESOURCE_CREATION_DATE));

         for (Integer key : props.keySet())
         {
            int keyValue = key.intValue() - MPPResourceField.RESOURCE_FIELD_BASE;
            //System.out.println("Key=" + keyValue);

            ResourceField field = MPPResourceField.getInstance(keyValue);

            if (field != null)
            {
               Object value = null;

               switch (field.getDataType())
               {
                  case CURRENCY:
                  {
                     value = Double.valueOf(props.getDouble(key) / 100);
                     break;
                  }

                  case DATE:
                  {
                     value = props.getTimestamp(key);
                     break;
                  }

                  case DURATION:
                  {
                     byte[] durationData = props.getByteArray(key);
                     double durationValueInHours = ((double) MPPUtility.getInt(durationData, 0)) / 600;
                     TimeUnit durationUnits;
                     if (durationData.length < 6)
                     {
                        durationUnits = TimeUnit.DAYS;
                     }
                     else
                     {
                        durationUnits = MPPUtility.getDurationTimeUnits(MPPUtility.getShort(durationData, 4));
                     }
                     Duration duration = Duration.getInstance(durationValueInHours, TimeUnit.HOURS);
                     value = duration.convertUnits(durationUnits, m_file.getProjectProperties());
                     break;

                  }

                  case BOOLEAN:
                  {
                     switch (field)
                     {
                        case FLAG1:
                        {
                           field = null;
                           int bits = props.getInt(key);
                           resource.set(ResourceField.ENTERPRISE_FLAG1, Boolean.valueOf((bits & 0x00002) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG2, Boolean.valueOf((bits & 0x00004) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG3, Boolean.valueOf((bits & 0x00008) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG4, Boolean.valueOf((bits & 0x00010) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG5, Boolean.valueOf((bits & 0x00020) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG6, Boolean.valueOf((bits & 0x00040) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG7, Boolean.valueOf((bits & 0x00080) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG8, Boolean.valueOf((bits & 0x00100) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG9, Boolean.valueOf((bits & 0x00200) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG10, Boolean.valueOf((bits & 0x00400) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG11, Boolean.valueOf((bits & 0x00800) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG12, Boolean.valueOf((bits & 0x01000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG13, Boolean.valueOf((bits & 0x02000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG14, Boolean.valueOf((bits & 0x04000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG15, Boolean.valueOf((bits & 0x08000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG16, Boolean.valueOf((bits & 0x10000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG17, Boolean.valueOf((bits & 0x20000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG18, Boolean.valueOf((bits & 0x40000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG19, Boolean.valueOf((bits & 0x80000) != 0));
                           resource.set(ResourceField.ENTERPRISE_FLAG20, Boolean.valueOf((bits & 0x100000) != 0));
                           break;
                        }

                        case GENERIC:
                        {
                           field = null;
                           resource.setGeneric(props.getShort(key) != 0);
                           break;
                        }

                        default:
                        {
                           break;
                        }
                     }

                     break;
                  }

                  case NUMERIC:
                  {
                     value = Double.valueOf(props.getDouble(key));
                     break;
                  }

                  case STRING:
                  {
                     value = props.getUnicodeString(key);
                     break;
                  }

                  default:
                  {
                     break;
                  }
               }

               resource.set(field, value);
            }
         }
      }
   }

   /**
    * The project files to which external tasks relate appear not to be
    * held against each task, instead there appears to be the concept
    * of the "current" external task file, i.e. the last one used.
    * This method iterates through the list of tasks marked as external
    * and attempts to ensure that the correct external project data (in the
    * form of a SubProject object) is linked to the task.
    *
    * @param externalTasks list of tasks marked as external
    */
   private void processExternalTasks(List<Task> externalTasks)
   {
      //
      // Sort the list of tasks into ID order
      //
      Collections.sort(externalTasks);

      //
      // Find any external tasks which don't have a sub project
      // object, and set this attribute using the most recent
      // value.
      //
      SubProject currentSubProject = null;

      for (Task currentTask : externalTasks)
      {
         SubProject sp = currentTask.getSubProject();
         if (sp == null)
         {
            currentTask.setSubProject(currentSubProject);

            //we need to set the external task project path now that we have
            //the subproject for this task (was skipped while processing the task earlier)
            if (currentSubProject != null)
            {
               currentTask.setExternalTaskProject(currentSubProject.getFullPath());
            }

         }
         else
         {
            currentSubProject = sp;
         }

         if (currentSubProject != null)
         {
            //System.out.println ("Task: " +currentTask.getUniqueID() + " " + currentTask.getName() + " File=" + currentSubProject.getFullPath() + " ID=" + currentTask.getExternalTaskID());
            currentTask.setProject(currentSubProject.getFullPath());
         }
      }
   }

   /**
    * This method is used to extract the task hyperlink attributes
    * from a block of data and call the appropriate modifier methods
    * to configure the specified task object.
    *
    * @param task task instance
    * @param data hyperlink data block
    */
   private void processHyperlinkData(Task task, byte[] data)
   {
      if (data != null)
      {
         int offset = 12;
         String hyperlink;
         String address;
         String subaddress;

         offset += 12;
         hyperlink = MPPUtility.getUnicodeString(data, offset);
         offset += ((hyperlink.length() + 1) * 2);

         offset += 12;
         address = MPPUtility.getUnicodeString(data, offset);
         offset += ((address.length() + 1) * 2);

         offset += 12;
         subaddress = MPPUtility.getUnicodeString(data, offset);

         task.setHyperlink(hyperlink);
         task.setHyperlinkAddress(address);
         task.setHyperlinkSubAddress(subaddress);
      }
   }

   /**
    * This method is used to extract the resource hyperlink attributes
    * from a block of data and call the appropriate modifier methods
    * to configure the specified task object.
    *
    * @param resource resource instance
    * @param data hyperlink data block
    */
   private void processHyperlinkData(Resource resource, byte[] data)
   {
      if (data != null)
      {
         int offset = 12;
         String hyperlink;
         String address;
         String subaddress;

         offset += 12;
         hyperlink = MPPUtility.getUnicodeString(data, offset);
         offset += ((hyperlink.length() + 1) * 2);

         offset += 12;
         address = MPPUtility.getUnicodeString(data, offset);
         offset += ((address.length() + 1) * 2);

         offset += 12;
         subaddress = MPPUtility.getUnicodeString(data, offset);

         resource.setHyperlink(hyperlink);
         resource.setHyperlinkAddress(address);
         resource.setHyperlinkSubAddress(subaddress);
      }
   }

   /**
    * This method extracts and collates constraint data.
    *
    * @throws IOException
    */
   private void processConstraintData() throws IOException
   {
      ConstraintFactory factory = new ConstraintFactory();
      factory.process(m_projectDir, m_file, m_inputStreamFactory);
   }

   /**
    * This method extracts and collates resource data.
    *
    * @throws IOException
    */
   private void processResourceData() throws IOException
   {
      FieldMap fieldMap = new FieldMap9(m_file.getProjectProperties(), m_file.getCustomFields());
      fieldMap.createResourceFieldMap(m_projectProps);

      DirectoryEntry rscDir = (DirectoryEntry) m_projectDir.getEntry("TBkndRsc");
      VarMeta rscVarMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) rscDir.getEntry("VarMeta"))));
      Var2Data rscVarData = new Var2Data(rscVarMeta, new DocumentInputStream(((DocumentEntry) rscDir.getEntry("Var2Data"))));
      FixedMeta rscFixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) rscDir.getEntry("FixedMeta"))), 37);
      FixedData rscFixedData = new FixedData(rscFixedMeta, m_inputStreamFactory.getInstance(rscDir, "FixedData"));
      //System.out.println(rscVarMeta);
      //System.out.println(rscVarData);
      //System.out.println(rscFixedMeta);
      //System.out.println(rscFixedData);

      processFieldNameAliases(RESOURCE_FIELD_ALIASES, m_projectProps.getByteArray(Props.RESOURCE_FIELD_NAME_ALIASES));

      TreeMap<Integer, Integer> resourceMap = createResourceMap(fieldMap, rscFixedMeta, rscFixedData);
      Integer[] uniqueid = rscVarMeta.getUniqueIdentifierArray();
      Integer id;
      Integer offset;
      byte[] data;
      byte[] metaData;
      Resource resource;
      String notes;

      for (int loop = 0; loop < uniqueid.length; loop++)
      {
         id = uniqueid[loop];
         offset = resourceMap.get(id);
         if (offset == null)
         {
            continue;
         }
         data = rscFixedData.getByteArrayValue(offset.intValue());

         //MPPUtility.dataDump(data, true, true, true, true, true, true, true);
         //MPPUtility.varDataDump(rscVarData, id, true, true, true, true, true, true);

         resource = m_file.addResource();

         resource.disableEvents();
         fieldMap.populateContainer(ResourceField.class, resource, id, new byte[][]
         {
            data
         }, rscVarData);
         resource.enableEvents();

         processHyperlinkData(resource, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.HYPERLINK_DATA)));
         resource.setID(Integer.valueOf(MPPUtility.getInt(data, 4)));

         resource.setOutlineCode1(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE1_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode2(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE2_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode3(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE3_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode4(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE4_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode5(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE5_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode6(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE6_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode7(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE7_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode8(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE8_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode9(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE9_INDEX), OUTLINECODE_DATA));
         resource.setOutlineCode10(m_outlineCodeVarData.getUnicodeString((Integer) resource.getCachedValue(ResourceField.OUTLINE_CODE10_INDEX), OUTLINECODE_DATA));

         resource.setUniqueID(id);

         metaData = rscFixedMeta.getByteArrayValue(offset.intValue());
         resource.setFlag(1, (metaData[28] & 0x40) != 0);
         resource.setFlag(2, (metaData[28] & 0x80) != 0);
         resource.setFlag(3, (metaData[29] & 0x01) != 0);
         resource.setFlag(4, (metaData[29] & 0x02) != 0);
         resource.setFlag(5, (metaData[29] & 0x04) != 0);
         resource.setFlag(6, (metaData[29] & 0x08) != 0);
         resource.setFlag(7, (metaData[29] & 0x10) != 0);
         resource.setFlag(8, (metaData[29] & 0x20) != 0);
         resource.setFlag(9, (metaData[29] & 0x40) != 0);
         resource.setFlag(10, (metaData[28] & 0x20) != 0);
         resource.setFlag(11, (metaData[29] & 0x80) != 0);
         resource.setFlag(12, (metaData[30] & 0x01) != 0);
         resource.setFlag(13, (metaData[30] & 0x02) != 0);
         resource.setFlag(14, (metaData[30] & 0x04) != 0);
         resource.setFlag(15, (metaData[30] & 0x08) != 0);
         resource.setFlag(16, (metaData[30] & 0x10) != 0);
         resource.setFlag(17, (metaData[30] & 0x20) != 0);
         resource.setFlag(18, (metaData[30] & 0x40) != 0);
         resource.setFlag(19, (metaData[30] & 0x80) != 0);
         resource.setFlag(20, (metaData[31] & 0x01) != 0);

         notes = resource.getNotes();
         if (m_reader.getPreserveNoteFormatting() == false)
         {
            notes = RtfHelper.strip(notes);
         }

         resource.setNotes(notes);

         //
         // Configure the resource calendar
         //
         resource.setResourceCalendar(m_resourceMap.get(id));

         //
         // Process any enterprise columns
         //
         processResourceEnterpriseColumns(fieldMap, resource, rscVarData);

         //
         // Process cost rate tables
         //
         CostRateTableFactory crt = new CostRateTableFactory();
         crt.process(resource, 0, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.COST_RATE_A)));
         crt.process(resource, 1, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.COST_RATE_B)));
         crt.process(resource, 2, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.COST_RATE_C)));
         crt.process(resource, 3, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.COST_RATE_D)));
         crt.process(resource, 4, rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.COST_RATE_E)));

         //
         // Process availability table
         //
         AvailabilityFactory af = new AvailabilityFactory();
         af.process(resource.getAvailability(), rscVarData.getByteArray(id, fieldMap.getVarDataKey(ResourceField.AVAILABILITY_DATA)));

         //
         // Process resource type
         //
         if ((metaData[9] & 0x02) != 0)
         {
            resource.setType(ResourceType.WORK);
         }
         else
         {
            resource.setType(ResourceType.MATERIAL);
         }

         m_eventManager.fireResourceReadEvent(resource);
      }
   }

   /**
    * This method extracts and collates resource assignment data.
    *
    * @throws IOException
    */
   private void processAssignmentData() throws IOException
   {
      FieldMap fieldMap = new FieldMap9(m_file.getProjectProperties(), m_file.getCustomFields());
      fieldMap.createAssignmentFieldMap(m_projectProps);

      DirectoryEntry assnDir = (DirectoryEntry) m_projectDir.getEntry("TBkndAssn");
      VarMeta assnVarMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) assnDir.getEntry("VarMeta"))));
      Var2Data assnVarData = new Var2Data(assnVarMeta, new DocumentInputStream(((DocumentEntry) assnDir.getEntry("Var2Data"))));

      FixedMeta assnFixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) assnDir.getEntry("FixedMeta"))), 34);
      FixedData assnFixedData = new FixedData(142, m_inputStreamFactory.getInstance(assnDir, "FixedData"));
      if (assnFixedData.getItemCount() != assnFixedMeta.getAdjustedItemCount())
      {
         assnFixedData = new FixedData(assnFixedMeta, m_inputStreamFactory.getInstance(assnDir, "FixedData"));
      }

      ResourceAssignmentFactory factory = new ResourceAssignmentFactory();
      factory.process(m_file, fieldMap, null, m_reader.getUseRawTimephasedData(), m_reader.getPreserveNoteFormatting(), assnVarMeta, assnVarData, assnFixedMeta, assnFixedData, null, assnFixedMeta.getAdjustedItemCount());
   }

   /**
    * This method is used to determine if a duration is estimated.
    *
    * @param type Duration units value
    * @return boolean Estimated flag
    */
   private boolean getDurationEstimated(int type)
   {
      return ((type & DURATION_CONFIRMED_MASK) != 0);
   }

   /**
    * This method extracts view data from the MPP file.
    *
    * @throws IOException
    */
   private void processViewData() throws IOException
   {
      DirectoryEntry dir = (DirectoryEntry) m_viewDir.getEntry("CV_iew");
      VarMeta viewVarMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) dir.getEntry("VarMeta"))));
      Var2Data viewVarData = new Var2Data(viewVarMeta, new DocumentInputStream(((DocumentEntry) dir.getEntry("Var2Data"))));
      FixedMeta fixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) dir.getEntry("FixedMeta"))), 10);
      FixedData fixedData = new FixedData(122, m_inputStreamFactory.getInstance(dir, "FixedData"));

      int items = fixedMeta.getAdjustedItemCount();
      View view;
      ViewFactory factory = new ViewFactory9();

      int lastOffset = -1;
      for (int loop = 0; loop < items; loop++)
      {
         byte[] fm = fixedMeta.getByteArrayValue(loop);
         int offset = MPPUtility.getShort(fm, 4);
         if (offset > lastOffset)
         {
            byte[] fd = fixedData.getByteArrayValue(fixedData.getIndexFromOffset(offset));
            if (fd != null)
            {
               view = factory.createView(m_file, fm, fd, viewVarData, m_fontBases);
               m_file.getViews().add(view);
               //System.out.print(view);
            }
            lastOffset = offset;
         }
      }
   }

   /**
    * This method extracts table data from the MPP file.
    *
    * @todo This implementation does not deal with MPP9 files saved by later
    * versions of MS Project
    *
    * @throws IOException
    */
   private void processTableData() throws IOException
   {
      DirectoryEntry dir = (DirectoryEntry) m_viewDir.getEntry("CTable");
      //FixedMeta fixedMeta = new FixedMeta(getEncryptableInputStream(dir, "FixedMeta"), 9);
      InputStream stream = m_inputStreamFactory.getInstance(dir, "FixedData");
      int blockSize = stream.available() % 115 == 0 ? 115 : 110;
      FixedData fixedData = new FixedData(blockSize, stream);
      VarMeta varMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) dir.getEntry("VarMeta"))));
      Var2Data varData = new Var2Data(varMeta, new DocumentInputStream(((DocumentEntry) dir.getEntry("Var2Data"))));

      TableContainer container = m_file.getTables();
      TableFactory factory = new TableFactory(TABLE_COLUMN_DATA_STANDARD, TABLE_COLUMN_DATA_ENTERPRISE, TABLE_COLUMN_DATA_BASELINE);
      int items = fixedData.getItemCount();
      for (int loop = 0; loop < items; loop++)
      {
         byte[] data = fixedData.getByteArrayValue(loop);
         Table table = factory.createTable(m_file, data, varMeta, varData);
         container.add(table);
         //System.out.println(table);
      }
   }

   /**
    * Read filter definitions.
    *
    * @todo Doesn't work correctly with MPP9 files saved by Propject 2007 and 2010
    * @throws IOException
    */
   private void processFilterData() throws IOException
   {
      DirectoryEntry dir = (DirectoryEntry) m_viewDir.getEntry("CFilter");
      //FixedMeta fixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) dir.getEntry("FixedMeta"))), 9);
      //FixedData fixedData = new FixedData(fixedMeta, getEncryptableInputStream(dir, "FixedData"));
      InputStream stream = m_inputStreamFactory.getInstance(dir, "FixedData");
      int blockSize = stream.available() % 115 == 0 ? 115 : 110;
      FixedData fixedData = new FixedData(blockSize, stream, true);
      VarMeta varMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) dir.getEntry("VarMeta"))));
      Var2Data varData = new Var2Data(varMeta, new DocumentInputStream(((DocumentEntry) dir.getEntry("Var2Data"))));

      //System.out.println(fixedMeta);
      //System.out.println(fixedData);
      //System.out.println(varMeta);
      //System.out.println(varData);

      FilterReader reader = new FilterReader9();
      reader.process(m_file.getProjectProperties(), m_file.getFilters(), fixedData, varData);
   }

   /**
    * Read group definitions.
    *
    * @todo Doesn't work correctly with MPP9 files saved by Propject 2007 and 2010
    * @throws IOException
    */
   private void processGroupData() throws IOException
   {
      DirectoryEntry dir = (DirectoryEntry) m_viewDir.getEntry("CGrouping");
      //FixedMeta fixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) dir.getEntry("FixedMeta"))), 9);
      FixedData fixedData = new FixedData(110, m_inputStreamFactory.getInstance(dir, "FixedData"));
      VarMeta varMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) dir.getEntry("VarMeta"))));
      Var2Data varData = new Var2Data(varMeta, new DocumentInputStream(((DocumentEntry) dir.getEntry("Var2Data"))));

      //      System.out.println(fixedMeta);
      //      System.out.println(fixedData);
      //      System.out.println(varMeta);
      //      System.out.println(varData);

      GroupReader reader = new GroupReader9();
      reader.process(m_file, fixedData, varData, m_fontBases);
   }

   /**
    * Read saved view state from an MPP file.
    *
    * @throws IOException
    */
   private void processSavedViewState() throws IOException
   {
      DirectoryEntry dir = (DirectoryEntry) m_viewDir.getEntry("CEdl");
      VarMeta varMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) dir.getEntry("VarMeta"))));
      Var2Data varData = new Var2Data(varMeta, new DocumentInputStream(((DocumentEntry) dir.getEntry("Var2Data"))));
      //System.out.println(varMeta);
      //System.out.println(varData);

      InputStream is = m_inputStreamFactory.getInstance(dir, "FixedData");
      byte[] fixedData = new byte[is.available()];
      is.read(fixedData);
      //System.out.println(ByteArrayHelper.hexdump(fixedData, false, 16, ""));

      ViewStateReader reader = new ViewStateReader9();
      reader.process(m_file, varData, fixedData);
   }

   /**
    * Read data link definitions.
    */
   private void processDataLinks() throws IOException
   {
      DirectoryEntry dir = (DirectoryEntry) m_viewDir.getEntry("CEdl");
      FixedMeta fixedMeta = new FixedMeta(new DocumentInputStream(((DocumentEntry) dir.getEntry("FixedMeta"))), 10);
      FixedData fixedData = new FixedData(fixedMeta, m_inputStreamFactory.getInstance(dir, "FixedData"));
      VarMeta varMeta = new VarMeta9(new DocumentInputStream(((DocumentEntry) dir.getEntry("VarMeta"))));
      Var2Data varData = new Var2Data(varMeta, new DocumentInputStream(((DocumentEntry) dir.getEntry("Var2Data"))));

      DataLinkFactory factory = new DataLinkFactory(m_file, fixedData, varData);
      factory.process();
   }

   /**
    * This method is called to try to catch any invalid tasks that may have sneaked past all our other checks.
    * This is done by validating the tasks by task ID.
    */
   //   private void postProcessTasks()
   //   {
   //      List<Task> allTasks = m_file.getTasks();
   //      if (allTasks.size() > 1)
   //      {
   //         Collections.sort(allTasks);
   //
   //         int taskID = -1;
   //         int lastTaskID = -1;
   //
   //         for (int i = 0; i < allTasks.size(); i++)
   //         {
   //            Task task = allTasks.get(i);
   //            taskID = NumberHelper.getInt(task.getID());
   //            // In Project the tasks IDs are always contiguous so we can spot invalid tasks by making sure all
   //            // IDs are represented.
   //            if (!task.getNull() && lastTaskID != -1 && taskID > lastTaskID + 1)
   //            {
   //               // This task looks to be invalid.
   //               task.setNull(true);
   //            }
   //            else
   //            {
   //               lastTaskID = taskID;
   //            }
   //         }
   //      }
   //   }

   private void postProcessTasks() throws MPXJException
   {
      //
      // Renumber ID values using a large increment to allow
      // space for later inserts.
      //
      TreeMap<Integer, Integer> taskMap = new TreeMap<>();
      int nextIDIncrement = ((m_nullTaskOrder.size() / 1000) + 1) * 2000;
      int nextID = (m_file.getTaskByUniqueID(Integer.valueOf(0)) == null ? nextIDIncrement : 0);
      for (Map.Entry<Integer, Integer> entry : m_taskOrder.entrySet())
      {
         taskMap.put(Integer.valueOf(nextID), entry.getValue());
         nextID += nextIDIncrement;
      }

      //
      // Insert any null tasks into the correct location
      //
      int insertionCount = 0;
      Map<Integer, Integer> offsetMap = new HashMap<>();
      for (Map.Entry<Integer, Integer> entry : m_nullTaskOrder.entrySet())
      {
         int idValue = entry.getKey().intValue();
         int baseTargetIdValue = (idValue - insertionCount) * nextIDIncrement;
         int targetIDValue = baseTargetIdValue;
         Integer previousOffsetKey = Integer.valueOf(baseTargetIdValue);
         Integer previousOffset = offsetMap.get(previousOffsetKey);
         int offset = previousOffset == null ? 0 : previousOffset.intValue() + 1;
         ++insertionCount;

         while (taskMap.containsKey(Integer.valueOf(targetIDValue)))
         {
            ++offset;
            if (offset == nextIDIncrement)
            {
               throw new MPXJException("Unable to fix task order");
            }
            targetIDValue = baseTargetIdValue - (nextIDIncrement - offset);
         }

         offsetMap.put(previousOffsetKey, Integer.valueOf(offset));
         taskMap.put(Integer.valueOf(targetIDValue), entry.getValue());
      }

      //
      // Finally, we can renumber the tasks
      //
      nextID = (m_file.getTaskByUniqueID(Integer.valueOf(0)) == null ? 1 : 0);
      for (Map.Entry<Integer, Integer> entry : taskMap.entrySet())
      {
         Task task = m_file.getTaskByUniqueID(entry.getValue());
         if (task != null)
         {
            task.setID(Integer.valueOf(nextID));
         }
         nextID++;
      }
   }

   //   private static void dumpUnknownData (String name, int[][] spec, byte[] data)
   //   {
   //      System.out.println (name);
   //      for (int loop=0; loop < spec.length; loop++)
   //      {
   //         System.out.println (spec[loop][0] + ": "+ ByteArrayHelper.hexdump(data, spec[loop][0], spec[loop][1], false));
   //      }
   //      System.out.println ();
   //   }

   //   private static final int[][] UNKNOWN_TASK_DATA = new int[][]
   //   {
   //      {36, 4},
   //      {42, 18},
   //      {116, 4},
   //      {134, 14},
   //      {144, 4},
   //      {148, 4},
   //      {152, 4},
   //      {156, 4},
   //      {248, 8},
   //   };

   //   private static final int[][] UNKNOWN_RESOURCE_DATA = new int[][]
   //   {
   //      {14, 6},
   //      {108, 16},
   //   };

   private MPPReader m_reader;
   private ProjectFile m_file;
   private EventManager m_eventManager;
   private DirectoryEntry m_root;
   private HashMap<Integer, ProjectCalendar> m_resourceMap;
   private Var2Data m_outlineCodeVarData;
   private Props9 m_projectProps;
   private Map<Integer, FontBase> m_fontBases;
   private Map<Integer, SubProject> m_taskSubProjects;
   private DirectoryEntry m_projectDir;
   private DirectoryEntry m_viewDir;
   private Map<Integer, Integer> m_taskOrder;
   private Map<Integer, Integer> m_nullTaskOrder;
   private DocumentInputStreamFactory m_inputStreamFactory;

   // Signals the end of the list of subproject task unique ids
   private static final int SUBPROJECT_LISTEND = 0x00000303;

   // Signals that the previous value was for the subproject task unique id
   private static final int SUBPROJECT_TASKUNIQUEID0 = 0x00000000;
   private static final int SUBPROJECT_TASKUNIQUEID1 = 0x0B340000;
   private static final int SUBPROJECT_TASKUNIQUEID2 = 0x0ABB0000;
   private static final int SUBPROJECT_TASKUNIQUEID3 = 0x05A10000;
   private static final int SUBPROJECT_TASKUNIQUEID4 = 0x02F70000;
   private static final int SUBPROJECT_TASKUNIQUEID5 = 0x07010000;

   private static final Integer TABLE_COLUMN_DATA_STANDARD = Integer.valueOf(1);
   private static final Integer TABLE_COLUMN_DATA_ENTERPRISE = Integer.valueOf(2);
   private static final Integer TABLE_COLUMN_DATA_BASELINE = null;
   private static final Integer OUTLINECODE_DATA = Integer.valueOf(1);

   /**
    * Mask used to isolate confirmed flag from the duration units field.
    */
   private static final int DURATION_CONFIRMED_MASK = 0x20;

   /**
    * Deleted and null tasks have their ID and UniqueID attributes at fixed offsets.
    */
   private static final int TASK_UNIQUE_ID_FIXED_OFFSET = 0;
   private static final int TASK_ID_FIXED_OFFSET = 4;
   private static final int NULL_TASK_BLOCK_SIZE = 8;

   private static final Map<Integer, FieldType> RESOURCE_FIELD_ALIASES = new HashMap<>();
   static
   {
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(52), ResourceField.TEXT1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(53), ResourceField.TEXT2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(54), ResourceField.TEXT3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(55), ResourceField.TEXT4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(56), ResourceField.TEXT5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(57), ResourceField.TEXT6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(58), ResourceField.TEXT7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(59), ResourceField.TEXT8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(60), ResourceField.TEXT9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(61), ResourceField.TEXT10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(62), ResourceField.TEXT11);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(63), ResourceField.TEXT12);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(64), ResourceField.TEXT13);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(65), ResourceField.TEXT14);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(66), ResourceField.TEXT15);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(67), ResourceField.TEXT16);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(68), ResourceField.TEXT17);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(69), ResourceField.TEXT18);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(70), ResourceField.TEXT19);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(71), ResourceField.TEXT20);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(72), ResourceField.TEXT21);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(73), ResourceField.TEXT22);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(74), ResourceField.TEXT23);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(75), ResourceField.TEXT24);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(76), ResourceField.TEXT25);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(77), ResourceField.TEXT26);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(78), ResourceField.TEXT27);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(79), ResourceField.TEXT28);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(80), ResourceField.TEXT29);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(81), ResourceField.TEXT30);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(82), ResourceField.START1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(83), ResourceField.START2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(84), ResourceField.START3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(85), ResourceField.START4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(86), ResourceField.START5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(87), ResourceField.START6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(88), ResourceField.START7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(89), ResourceField.START8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(90), ResourceField.START9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(91), ResourceField.START10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(92), ResourceField.FINISH1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(93), ResourceField.FINISH2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(94), ResourceField.FINISH3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(95), ResourceField.FINISH4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(96), ResourceField.FINISH5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(97), ResourceField.FINISH6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(98), ResourceField.FINISH7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(99), ResourceField.FINISH8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(100), ResourceField.FINISH9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(101), ResourceField.FINISH10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(102), ResourceField.NUMBER1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(103), ResourceField.NUMBER2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(104), ResourceField.NUMBER3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(105), ResourceField.NUMBER4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(106), ResourceField.NUMBER5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(107), ResourceField.NUMBER6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(108), ResourceField.NUMBER7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(109), ResourceField.NUMBER8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(110), ResourceField.NUMBER9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(111), ResourceField.NUMBER10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(112), ResourceField.NUMBER11);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(113), ResourceField.NUMBER12);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(114), ResourceField.NUMBER13);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(115), ResourceField.NUMBER14);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(116), ResourceField.NUMBER15);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(117), ResourceField.NUMBER16);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(118), ResourceField.NUMBER17);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(119), ResourceField.NUMBER18);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(120), ResourceField.NUMBER19);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(121), ResourceField.NUMBER20);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(122), ResourceField.DURATION1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(123), ResourceField.DURATION2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(124), ResourceField.DURATION3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(125), ResourceField.DURATION4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(126), ResourceField.DURATION5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(127), ResourceField.DURATION6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(128), ResourceField.DURATION7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(129), ResourceField.DURATION8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(130), ResourceField.DURATION9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(131), ResourceField.DURATION10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(145), ResourceField.DATE1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(146), ResourceField.DATE2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(147), ResourceField.DATE3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(148), ResourceField.DATE4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(149), ResourceField.DATE5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(150), ResourceField.DATE6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(151), ResourceField.DATE7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(152), ResourceField.DATE8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(153), ResourceField.DATE9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(154), ResourceField.DATE10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(155), ResourceField.OUTLINE_CODE1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(156), ResourceField.OUTLINE_CODE2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(157), ResourceField.OUTLINE_CODE3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(158), ResourceField.OUTLINE_CODE4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(159), ResourceField.OUTLINE_CODE5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(160), ResourceField.OUTLINE_CODE6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(161), ResourceField.OUTLINE_CODE7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(162), ResourceField.OUTLINE_CODE8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(163), ResourceField.OUTLINE_CODE9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(164), ResourceField.OUTLINE_CODE10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(165), ResourceField.FLAG10);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(166), ResourceField.FLAG1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(167), ResourceField.FLAG2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(168), ResourceField.FLAG3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(169), ResourceField.FLAG4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(170), ResourceField.FLAG5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(171), ResourceField.FLAG6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(172), ResourceField.FLAG7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(173), ResourceField.FLAG8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(174), ResourceField.FLAG9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(175), ResourceField.FLAG11);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(176), ResourceField.FLAG12);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(177), ResourceField.FLAG13);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(178), ResourceField.FLAG14);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(179), ResourceField.FLAG15);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(180), ResourceField.FLAG16);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(181), ResourceField.FLAG17);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(182), ResourceField.FLAG18);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(183), ResourceField.FLAG19);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(184), ResourceField.FLAG20);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(207), ResourceField.COST1);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(208), ResourceField.COST2);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(209), ResourceField.COST3);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(210), ResourceField.COST4);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(211), ResourceField.COST5);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(212), ResourceField.COST6);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(213), ResourceField.COST7);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(214), ResourceField.COST8);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(215), ResourceField.COST9);
      RESOURCE_FIELD_ALIASES.put(Integer.valueOf(216), ResourceField.COST10);
   }

   private static final Map<Integer, FieldType> TASK_FIELD_ALIASES = new HashMap<>();
   static
   {
      TASK_FIELD_ALIASES.put(Integer.valueOf(118), TaskField.TEXT1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(119), TaskField.TEXT2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(120), TaskField.TEXT3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(121), TaskField.TEXT4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(122), TaskField.TEXT5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(123), TaskField.TEXT6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(124), TaskField.TEXT7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(125), TaskField.TEXT8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(126), TaskField.TEXT9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(127), TaskField.TEXT10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(128), TaskField.START1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(129), TaskField.FINISH1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(130), TaskField.START2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(131), TaskField.FINISH2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(132), TaskField.START3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(133), TaskField.FINISH3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(134), TaskField.START4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(135), TaskField.FINISH4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(136), TaskField.START5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(137), TaskField.FINISH5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(138), TaskField.START6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(139), TaskField.FINISH6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(140), TaskField.START7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(141), TaskField.FINISH7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(142), TaskField.START8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(143), TaskField.FINISH8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(144), TaskField.START9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(145), TaskField.FINISH9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(146), TaskField.START10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(147), TaskField.FINISH10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(149), TaskField.NUMBER1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(150), TaskField.NUMBER2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(151), TaskField.NUMBER3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(152), TaskField.NUMBER4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(153), TaskField.NUMBER5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(154), TaskField.NUMBER6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(155), TaskField.NUMBER7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(156), TaskField.NUMBER8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(157), TaskField.NUMBER9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(158), TaskField.NUMBER10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(159), TaskField.DURATION1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(161), TaskField.DURATION2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(163), TaskField.DURATION3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(165), TaskField.DURATION4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(167), TaskField.DURATION5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(169), TaskField.DURATION6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(171), TaskField.DURATION7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(173), TaskField.DURATION8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(175), TaskField.DURATION9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(177), TaskField.DURATION10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(184), TaskField.DATE1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(185), TaskField.DATE2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(186), TaskField.DATE3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(187), TaskField.DATE4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(188), TaskField.DATE5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(189), TaskField.DATE6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(190), TaskField.DATE7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(191), TaskField.DATE8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(192), TaskField.DATE9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(193), TaskField.DATE10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(194), TaskField.TEXT11);
      TASK_FIELD_ALIASES.put(Integer.valueOf(195), TaskField.TEXT12);
      TASK_FIELD_ALIASES.put(Integer.valueOf(196), TaskField.TEXT13);
      TASK_FIELD_ALIASES.put(Integer.valueOf(197), TaskField.TEXT14);
      TASK_FIELD_ALIASES.put(Integer.valueOf(198), TaskField.TEXT15);
      TASK_FIELD_ALIASES.put(Integer.valueOf(199), TaskField.TEXT16);
      TASK_FIELD_ALIASES.put(Integer.valueOf(200), TaskField.TEXT17);
      TASK_FIELD_ALIASES.put(Integer.valueOf(201), TaskField.TEXT18);
      TASK_FIELD_ALIASES.put(Integer.valueOf(202), TaskField.TEXT19);
      TASK_FIELD_ALIASES.put(Integer.valueOf(203), TaskField.TEXT20);
      TASK_FIELD_ALIASES.put(Integer.valueOf(204), TaskField.TEXT21);
      TASK_FIELD_ALIASES.put(Integer.valueOf(205), TaskField.TEXT22);
      TASK_FIELD_ALIASES.put(Integer.valueOf(206), TaskField.TEXT23);
      TASK_FIELD_ALIASES.put(Integer.valueOf(207), TaskField.TEXT24);
      TASK_FIELD_ALIASES.put(Integer.valueOf(208), TaskField.TEXT25);
      TASK_FIELD_ALIASES.put(Integer.valueOf(209), TaskField.TEXT26);
      TASK_FIELD_ALIASES.put(Integer.valueOf(210), TaskField.TEXT27);
      TASK_FIELD_ALIASES.put(Integer.valueOf(211), TaskField.TEXT28);
      TASK_FIELD_ALIASES.put(Integer.valueOf(212), TaskField.TEXT29);
      TASK_FIELD_ALIASES.put(Integer.valueOf(213), TaskField.TEXT30);
      TASK_FIELD_ALIASES.put(Integer.valueOf(214), TaskField.NUMBER11);
      TASK_FIELD_ALIASES.put(Integer.valueOf(215), TaskField.NUMBER12);
      TASK_FIELD_ALIASES.put(Integer.valueOf(216), TaskField.NUMBER13);
      TASK_FIELD_ALIASES.put(Integer.valueOf(217), TaskField.NUMBER14);
      TASK_FIELD_ALIASES.put(Integer.valueOf(218), TaskField.NUMBER15);
      TASK_FIELD_ALIASES.put(Integer.valueOf(219), TaskField.NUMBER16);
      TASK_FIELD_ALIASES.put(Integer.valueOf(220), TaskField.NUMBER17);
      TASK_FIELD_ALIASES.put(Integer.valueOf(221), TaskField.NUMBER18);
      TASK_FIELD_ALIASES.put(Integer.valueOf(222), TaskField.NUMBER19);
      TASK_FIELD_ALIASES.put(Integer.valueOf(223), TaskField.NUMBER20);
      TASK_FIELD_ALIASES.put(Integer.valueOf(227), TaskField.OUTLINE_CODE1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(228), TaskField.OUTLINE_CODE2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(229), TaskField.OUTLINE_CODE3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(230), TaskField.OUTLINE_CODE4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(231), TaskField.OUTLINE_CODE5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(232), TaskField.OUTLINE_CODE6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(233), TaskField.OUTLINE_CODE7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(234), TaskField.OUTLINE_CODE8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(235), TaskField.OUTLINE_CODE9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(236), TaskField.OUTLINE_CODE10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(237), TaskField.FLAG1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(238), TaskField.FLAG2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(239), TaskField.FLAG3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(240), TaskField.FLAG4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(241), TaskField.FLAG5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(242), TaskField.FLAG6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(243), TaskField.FLAG7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(244), TaskField.FLAG8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(245), TaskField.FLAG9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(246), TaskField.FLAG10);
      TASK_FIELD_ALIASES.put(Integer.valueOf(247), TaskField.FLAG11);
      TASK_FIELD_ALIASES.put(Integer.valueOf(248), TaskField.FLAG12);
      TASK_FIELD_ALIASES.put(Integer.valueOf(249), TaskField.FLAG13);
      TASK_FIELD_ALIASES.put(Integer.valueOf(250), TaskField.FLAG14);
      TASK_FIELD_ALIASES.put(Integer.valueOf(251), TaskField.FLAG15);
      TASK_FIELD_ALIASES.put(Integer.valueOf(252), TaskField.FLAG16);
      TASK_FIELD_ALIASES.put(Integer.valueOf(253), TaskField.FLAG17);
      TASK_FIELD_ALIASES.put(Integer.valueOf(254), TaskField.FLAG18);
      TASK_FIELD_ALIASES.put(Integer.valueOf(255), TaskField.FLAG19);
      TASK_FIELD_ALIASES.put(Integer.valueOf(256), TaskField.FLAG20);
      TASK_FIELD_ALIASES.put(Integer.valueOf(278), TaskField.COST1);
      TASK_FIELD_ALIASES.put(Integer.valueOf(279), TaskField.COST2);
      TASK_FIELD_ALIASES.put(Integer.valueOf(280), TaskField.COST3);
      TASK_FIELD_ALIASES.put(Integer.valueOf(281), TaskField.COST4);
      TASK_FIELD_ALIASES.put(Integer.valueOf(282), TaskField.COST5);
      TASK_FIELD_ALIASES.put(Integer.valueOf(283), TaskField.COST6);
      TASK_FIELD_ALIASES.put(Integer.valueOf(284), TaskField.COST7);
      TASK_FIELD_ALIASES.put(Integer.valueOf(285), TaskField.COST8);
      TASK_FIELD_ALIASES.put(Integer.valueOf(286), TaskField.COST9);
      TASK_FIELD_ALIASES.put(Integer.valueOf(287), TaskField.COST10);
   }

}