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

net.sf.mpxj.mpp.AbstractCalendarAndExceptionFactory 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.6.0
Show newest version
/*
 * file:       AbstractCalendarAndExceptionFactory.java
 * author:     Jon Iles
 * copyright:  (c) Packwood Software 2017
 * date:       2017-10-04
 */

/*
 * 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.time.LocalDate;
import java.time.LocalTime;
import java.time.temporal.ChronoUnit;

import java.time.DayOfWeek;
import net.sf.mpxj.common.DayOfWeekHelper;
import net.sf.mpxj.DayType;
import net.sf.mpxj.LocalDateRange;
import net.sf.mpxj.ProjectCalendar;
import net.sf.mpxj.ProjectCalendarException;
import net.sf.mpxj.ProjectCalendarHours;
import net.sf.mpxj.ProjectCalendarWeek;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.RecurrenceType;
import net.sf.mpxj.RecurringData;
import net.sf.mpxj.LocalTimeRange;
import net.sf.mpxj.common.LocalDateHelper;
import net.sf.mpxj.common.NumberHelper;

/**
 * Shared code used to read calendar data from MPP files.
 */
abstract class AbstractCalendarAndExceptionFactory extends AbstractCalendarFactory
{
   /**
    * Constructor.
    *
    * @param file parent ProjectFile instance
    */
   public AbstractCalendarAndExceptionFactory(ProjectFile file)
   {
      super(file);
   }

   /**
    * This method extracts any exceptions associated with a calendar.
    *
    * @param data calendar data block
    * @param cal calendar instance
    */
   @Override protected void processCalendarExceptions(byte[] data, ProjectCalendar cal)
   {
      //
      // Handle any exceptions
      //
      if (data.length > 420)
      {
         int offset = 420; // The first 420 is for the working hours data

         int exceptionCount = MPPUtility.getShort(data, offset);

         if (exceptionCount == 0)
         {
            // align with 8 byte boundary ready to read work weeks
            offset += 4;
         }
         else
         {
            ProjectCalendarException exception;

            //
            // Move to the start of the first exception
            //
            offset += 4;

            //
            // Each exception is a 92 byte block, followed by a
            // variable length text block
            //
            for (int index = 0; index < exceptionCount; index++)
            {
               if (offset + 92 > data.length)
               {
                  // Bail out if we don't have at least 92 bytes available
                  break;
               }

               LocalDate fromDate = LocalDateHelper.getLocalDate(MPPUtility.getDate(data, offset));
               LocalDate toDate = LocalDateHelper.getLocalDate(MPPUtility.getDate(data, offset + 2));
               RecurringData rd = readRecurringData(data, offset, fromDate, toDate);
               if (rd == null)
               {
                  exception = cal.addCalendarException(fromDate, toDate);
               }
               else
               {
                  exception = cal.addCalendarException(rd);
               }

               int periodCount = MPPUtility.getShort(data, offset + 14);
               if (periodCount != 0)
               {
                  for (int exceptionPeriodIndex = 0; exceptionPeriodIndex < periodCount; exceptionPeriodIndex++)
                  {
                     LocalTime start = MPPUtility.getTime(data, offset + 20 + (exceptionPeriodIndex * 2));
                     long duration = MPPUtility.getDuration(data, offset + 32 + (exceptionPeriodIndex * 4));
                     LocalTime end = start.plus(duration, ChronoUnit.MILLIS);
                     exception.add(new LocalTimeRange(start, end));
                  }
               }

               //
               // Extract the name length - ensure that it is aligned to a 4 byte boundary
               //
               int exceptionNameLength = MPPUtility.getInt(data, offset + 88);
               if (exceptionNameLength % 4 != 0)
               {
                  exceptionNameLength = ((exceptionNameLength / 4) + 1) * 4;
               }

               if (exceptionNameLength != 0)
               {
                  exception.setName(MPPUtility.getUnicodeString(data, offset + 92));
               }

               offset += (92 + exceptionNameLength);
            }
         }

         processWorkWeeks(data, offset, cal);
      }
   }

   private RecurringData readRecurringData(byte[] data, int offset, LocalDate fromDate, LocalDate toDate)
   {
      RecurringData rd = new RecurringData();
      int recurrenceTypeValue = MPPUtility.getShort(data, offset + 72);
      rd.setStartDate(fromDate);
      rd.setFinishDate(toDate);
      rd.setRecurrenceType(getRecurrenceType(recurrenceTypeValue));
      rd.setRelative(getRelative(recurrenceTypeValue));
      rd.setOccurrences(Integer.valueOf(MPPUtility.getShort(data, offset + 4)));

      switch (rd.getRecurrenceType())
      {
         case DAILY:
         {
            int frequency;
            if (recurrenceTypeValue == 1)
            {
               frequency = 1;
            }
            else
            {
               frequency = MPPUtility.getShort(data, offset + 76);
            }
            rd.setFrequency(Integer.valueOf(frequency));
            break;
         }

         case WEEKLY:
         {
            rd.setWeeklyDaysFromBitmap(Integer.valueOf(MPPUtility.getByte(data, offset + 76)), DAY_MASKS);
            rd.setFrequency(Integer.valueOf(MPPUtility.getShort(data, offset + 78)));
            break;
         }

         case MONTHLY:
         {
            if (rd.getRelative())
            {
               rd.setDayOfWeek(DayOfWeekHelper.getInstance(MPPUtility.getByte(data, offset + 77) - 2));
               rd.setDayNumber(Integer.valueOf(MPPUtility.getByte(data, offset + 76) + 1));
               rd.setFrequency(Integer.valueOf(MPPUtility.getShort(data, offset + 78)));
            }
            else
            {
               rd.setDayNumber(Integer.valueOf(MPPUtility.getByte(data, offset + 76)));
               rd.setFrequency(Integer.valueOf(MPPUtility.getByte(data, offset + 78)));
            }
            break;
         }

         case YEARLY:
         {
            if (rd.getRelative())
            {
               rd.setDayOfWeek(DayOfWeekHelper.getInstance(MPPUtility.getByte(data, offset + 78) - 2));
               rd.setDayNumber(Integer.valueOf(MPPUtility.getByte(data, offset + 77) + 1));
            }
            else
            {
               rd.setDayNumber(Integer.valueOf(MPPUtility.getByte(data, offset + 77)));
            }
            rd.setMonthNumber(Integer.valueOf(MPPUtility.getByte(data, offset + 76) + 1));
            break;
         }
      }

      //
      // Flatten daily recurring exceptions if they only result in one date range.
      // Flatten exception if it doesn't generate any dates.
      //
      if (rd.getRecurrenceType() == RecurrenceType.DAILY && NumberHelper.getInt(rd.getFrequency()) == 1)
      {
         rd = null;
      }

      return rd;
   }

   /**
    * Read the work weeks.
    *
    * @param data calendar data
    * @param offset current offset into data
    * @param cal parent calendar
    */
   private void processWorkWeeks(byte[] data, int offset, ProjectCalendar cal)
   {
      //      System.out.println("Calendar=" + cal.getName());
      //      System.out.println("Work week block start offset=" + offset);
      //      System.out.println(ByteArrayHelper.hexdump(data, true, 16, ""));

      // skip 4 byte header
      offset += 4;

      while (data.length >= offset + ((7 * 60) + 2 + 2 + 8 + 4))
      {
         //System.out.println("Week start offset=" + offset);
         ProjectCalendarWeek week = cal.addWorkWeek();
         for (DayOfWeek day : DayOfWeekHelper.ORDERED_DAYS)
         {
            // 60 byte block per day
            processWorkWeekDay(data, offset, week, day);
            offset += 60;
         }

         LocalDate startDate = LocalDateHelper.getLocalDate(MPPUtility.getDate(data, offset));
         offset += 2;

         LocalDate finishDate = LocalDateHelper.getLocalDate(MPPUtility.getDate(data, offset));
         offset += 2;

         // skip unknown 8 bytes
         //System.out.println(ByteArrayHelper.hexdump(data, offset, 8, false));
         offset += 8;

         //
         // Extract the name length - ensure that it is aligned to a 4 byte boundary
         //
         int nameLength = MPPUtility.getInt(data, offset);
         if (nameLength % 4 != 0)
         {
            nameLength = ((nameLength / 4) + 1) * 4;
         }
         offset += 4;

         if (nameLength != 0)
         {
            String name = MPPUtility.getUnicodeString(data, offset, nameLength);
            offset += nameLength;
            week.setName(name);
         }

         week.setDateRange(new LocalDateRange(startDate, finishDate));
         // System.out.println(week);
      }
   }

   /**
    * Process an individual work week day.
    *
    * @param data calendar data
    * @param offset current offset into data
    * @param week parent week
    * @param day current day
    */
   private void processWorkWeekDay(byte[] data, int offset, ProjectCalendarWeek week, DayOfWeek day)
   {
      //System.out.println(ByteArrayHelper.hexdump(data, offset, 60, false));

      int dayType = MPPUtility.getShort(data, offset);
      if (dayType == 1)
      {
         week.setCalendarDayType(day, DayType.DEFAULT);
      }
      else
      {
         ProjectCalendarHours hours = week.addCalendarHours(day);
         int rangeCount = MPPUtility.getShort(data, offset + 2);
         if (rangeCount == 0)
         {
            week.setCalendarDayType(day, DayType.NON_WORKING);
         }
         else
         {
            week.setCalendarDayType(day, DayType.WORKING);
            for (int index = 0; index < rangeCount; index++)
            {
               LocalTime startTime = MPPUtility.getTime(data, offset + 8 + (index * 2));
               int durationInSeconds = MPPUtility.getInt(data, offset + 20 + (index * 4)) * 6;
               LocalTime finishTime = startTime.plusSeconds(durationInSeconds);
               hours.add(new LocalTimeRange(startTime, finishTime));
            }
         }
      }
   }

   /**
    * Retrieve the recurrence type.
    *
    * @param value integer value
    * @return RecurrenceType instance
    */
   private RecurrenceType getRecurrenceType(int value)
   {
      RecurrenceType result;
      if (value < 0 || value >= RECURRENCE_TYPES.length)
      {
         result = null;
      }
      else
      {
         result = RECURRENCE_TYPES[value];
      }

      return result;
   }

   /**
    * Determine if the exception is relative based on the recurrence type integer value.
    *
    * @param value integer value
    * @return true if the recurrence is relative
    */
   private boolean getRelative(int value)
   {
      boolean result;
      if (value < 0 || value >= RELATIVE_MAP.length)
      {
         result = false;
      }
      else
      {
         result = RELATIVE_MAP[value];
      }

      return result;
   }

   private static final RecurrenceType[] RECURRENCE_TYPES =
   {
      null,
      RecurrenceType.DAILY,
      RecurrenceType.YEARLY, // Absolute
      RecurrenceType.YEARLY, // Relative
      RecurrenceType.MONTHLY, // Absolute
      RecurrenceType.MONTHLY, // Relative
      RecurrenceType.WEEKLY,
      RecurrenceType.DAILY
   };

   private static final boolean[] RELATIVE_MAP =
   {
      false,
      false,
      false,
      true,
      false,
      true
   };

   private static final int[] DAY_MASKS =
   {
      0x00,
      0x01, // Sunday
      0x02, // Monday
      0x04, // Tuesday
      0x08, // Wednesday
      0x10, // Thursday
      0x20, // Friday
      0x40, // Saturday
   };

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy