All Downloads are FREE. Search and download functionalities are using the official Maven repository.

net.sf.mpxj.sage.SageReader Maven / Gradle / Ivy

Go to download

Library that provides facilities to allow project information to be manipulated in Java and .Net. Supports a range of data formats: Microsoft Project Exchange (MPX), Microsoft Project (MPP,MPT), Microsoft Project Data Interchange (MSPDI XML), Microsoft Project Database (MPD), Planner (XML), Primavera (PM XML, XER, and database), Asta Powerproject (PP, MDB), Asta Easyplan (PP), Phoenix Project Manager (PPX), FastTrack Schedule (FTS), and the Standard Data Exchange Format (SDEF).

There is a newer version: 13.4.0
Show newest version
/*
 * file:       SageReader.java
 * author:     Jon Iles
 * copyright:  (c) Packwood Software 2019
 * date:       2019-11-08
 */

/*
 * 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.sage;

import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.time.LocalDate;
import java.time.LocalDateTime;
import java.time.format.DateTimeFormatter;
import java.time.format.DateTimeParseException;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import net.sf.mpxj.ConstraintType;
import net.sf.mpxj.Duration;
import net.sf.mpxj.EventManager;
import net.sf.mpxj.MPXJException;
import net.sf.mpxj.ProjectCalendar;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.Relation;
import net.sf.mpxj.RelationType;
import net.sf.mpxj.Task;
import net.sf.mpxj.TimeUnit;
import net.sf.mpxj.common.SlackHelper;
import net.sf.mpxj.reader.AbstractProjectStreamReader;

/**
 * Read schedule grid files generated by Sage 100 Contractor.
 */
public final class SageReader extends AbstractProjectStreamReader
{
   @Override public ProjectFile read(InputStream is) throws MPXJException
   {
      try
      {
         m_projectFile = new ProjectFile();
         m_projectFile.getProjectProperties().setFileApplication("Sage");
         m_projectFile.getProjectProperties().setFileType("SCHEDULE_GRID");
         m_eventManager = m_projectFile.getEventManager();
         m_taskMap = new HashMap<>();

         BufferedReader reader = new BufferedReader(new InputStreamReader(is));
         List lines = new ArrayList<>();
         String line;

         while ((line = reader.readLine()) != null)
         {
            lines.add(line);
         }

         processCalendars();
         processTasks(lines);
         processPredecessors(lines);
         m_projectFile.readComplete();

         return m_projectFile;
      }

      catch (IOException ex)
      {
         throw new MPXJException(MPXJException.READ_ERROR, ex);
      }

      finally
      {
         m_eventManager = null;
         m_taskMap = null;
      }
   }

   /**
    * Set a flag to determine if datatype parse errors can be ignored.
    * Defaults to true.
    *
    * @param ignoreErrors pass true to ignore errors
    */
   public void setIgnoreErrors(boolean ignoreErrors)
   {
      m_ignoreErrors = ignoreErrors;
   }

   /**
    * Retrieve the flag which determines if datatype parse errors can be ignored.
    * Defaults to true.
    *
    * @return true if datatype parse errors are ignored
    */
   public boolean getIgnoreErrors()
   {
      return m_ignoreErrors;
   }

   private void processCalendars()
   {
      ProjectCalendar defaultCalendar = m_projectFile.addDefaultBaseCalendar();
      m_projectFile.getProjectProperties().setDefaultCalendar(defaultCalendar);
   }

   /**
    * Locate the tasks section and process.
    *
    * @param lines file content
    */
   private void processTasks(List lines)
   {
      int index = skipToSection(lines, "**** Schedule Grid ****");
      if (index == lines.size())
      {
         return;
      }

      while (index < lines.size())
      {
         String line = lines.get(index++);
         if (line.isEmpty())
         {
            return;
         }

         processTask(line);
      }
   }

   /**
    * Locate the predecessors section and process.
    *
    * @param lines file content
    */
   private void processPredecessors(List lines)
   {
      int index = skipToSection(lines, "**** Predecessors ****");
      if (index == lines.size())
      {
         return;
      }

      while (index < lines.size())
      {
         String line = lines.get(index++);
         if (line.isEmpty())
         {
            return;
         }

         processPredecessor(line);
      }
   }

   /**
    * Locate the start of a specific section.
    *
    * @param lines file content
    * @param section section text
    * @return section index
    */
   private int skipToSection(List lines, String section)
   {
      int index = 0;

      while (index < lines.size())
      {
         String line = lines.get(index++);
         if (line.equals(section))
         {
            break;
         }
      }

      return index;
   }

   /**
    * Process an individual task.
    *
    * @param line task record
    */
   private void processTask(String line)
   {
      String[] columns = line.split("\t");
      Task task = m_projectFile.addTask();

      task.setText(1, parseID(columns, 0));
      task.setName(getText(columns, 1));
      task.setDuration(parseDuration(columns, 2));
      // columns[3] task type, 1 - Work, 2 - Inspection, 3 - Bill, 4 - Milestone, 5 - Order Material, 6 - Owner Decision, 7 - Meeting, 8 - Subcontract
      setConstraint(task, ConstraintType.MUST_START_ON, columns, 4);
      setConstraint(task, ConstraintType.START_NO_EARLIER_THAN, columns, 5);
      setConstraint(task, ConstraintType.FINISH_NO_LATER_THAN, columns, 6);
      task.setStart(parseDate(columns, 7));
      task.setFinish(parseDate(columns, 8));
      task.setLateStart(parseDate(columns, 9));
      task.setLateFinish(parseDate(columns, 10));
      // set total slack later to avoid calculation issues
      task.setBaselineDuration(parseDuration(columns, 12));
      task.setBaselineStart(parseDate(columns, 13));
      task.setBaselineFinish(parseDate(columns, 14));
      // columns[15] original float
      task.setText(2, getText(columns, 16));
      task.setNotes(getText(columns, 17));
      task.setTotalSlack(parseDuration(columns, 11));

      //
      // The schedule only includes total slack. We'll assume this value is correct and backfill start and finish slack values.
      //
      SlackHelper.inferSlack(task);

      m_taskMap.put(task.getText(1), task);
      m_eventManager.fireTaskReadEvent(task);
   }

   /**
    * Process an individual predecessor.
    *
    * @param line predecessor record
    */
   private void processPredecessor(String line)
   {
      String[] columns = line.split("\t");

      Task task = m_taskMap.get(parseID(columns, 0));
      if (task == null)
      {
         return;
      }

      Task predecessor = m_taskMap.get(parseID(columns, 1));
      if (predecessor == null)
      {
         return;
      }

      // columns[4] - job
      // columns[5] - predecessor name
      // columns[6] - unknown
      // columns[7] - unknown

      task.addPredecessor(new Relation.Builder()
         .targetTask(predecessor)
         .type(parseRelationType(columns, 2))
         .lag(parseDuration(columns, 3)));
   }

   /**
    * Parse an ID from a text field.
    *
    * @param columns record
    * @param index field index
    * @return ID value
    */
   private String parseID(String[] columns, int index)
   {
      String id = getText(columns, index);
      if (id != null && id.indexOf('.') == -1)
      {
         id = id + ".000";
      }
      return id;
   }

   /**
    * Parse a text value from a record.
    *
    * @param columns record
    * @param index field index
    * @return text value
    */
   private String getText(String[] columns, int index)
   {
      String result = null;
      if (index < columns.length)
      {
         result = columns[index];
      }
      return result;
   }

   /**
    * Parse a date value from a record.
    *
    * @param columns record
    * @param index field index
    * @return date value
    */
   private LocalDateTime parseDate(String[] columns, int index)
   {
      LocalDateTime result;

      String date = getText(columns, index);
      if (date == null || date.isEmpty())
      {
         result = null;
      }
      else
      {
         try
         {
            result = LocalDate.parse(date, DATE_FORMAT).atStartOfDay();
         }

         catch (DateTimeParseException ex)
         {
            if (m_ignoreErrors)
            {
               result = null;
               m_projectFile.addIgnoredError(ex);
            }
            else
            {
               throw ex;
            }
         }
      }
      return result;
   }

   /**
    * Parse a duration value from a record.
    *
    * @param columns record
    * @param index field index
    * @return duration value
    */
   private Duration parseDuration(String[] columns, int index)
   {
      Duration result = null;

      String duration = getText(columns, index);
      if (duration != null && !duration.isEmpty())
      {
         result = Duration.getInstance(Integer.parseInt(duration), TimeUnit.DAYS);
      }
      return result;
   }

   /**
    * Parse a relationship type value from a record.
    *
    * @param columns record
    * @param index field index
    * @return relationship type value
    */
   private RelationType parseRelationType(String[] columns, int index)
   {
      RelationType result = null;
      String text = getText(columns, index);
      if (text != null)
      {
         result = RELATION_TYPE_MAP.get(text);
      }
      if (result == null)
      {
         result = RelationType.FINISH_START;
      }
      return result;
   }

   /**
    * Set a task constraint if a constraint dat has been supplied.
    *
    * @param task Task instance
    * @param type constraint type
    * @param columns record
    * @param index constraint date field index
    */
   private void setConstraint(Task task, ConstraintType type, String[] columns, int index)
   {
      LocalDateTime date = parseDate(columns, index);
      if (date != null)
      {
         task.setConstraintType(type);
         task.setConstraintDate(date);
      }
   }

   private ProjectFile m_projectFile;
   private EventManager m_eventManager;
   private Map m_taskMap;
   private boolean m_ignoreErrors = true;

   private static final DateTimeFormatter DATE_FORMAT = DateTimeFormatter.ofPattern("MM/dd/yyyy");

   private static final Map RELATION_TYPE_MAP = new HashMap<>();
   static
   {
      RELATION_TYPE_MAP.put("1", RelationType.FINISH_START);
      RELATION_TYPE_MAP.put("2", RelationType.START_START);
      RELATION_TYPE_MAP.put("3", RelationType.START_FINISH);
      RELATION_TYPE_MAP.put("4", RelationType.FINISH_FINISH);
   }
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy