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

net.sf.mpxj.mpp.AbstractCalendarAndExceptionFactory Maven / Gradle / Ivy

/*
 * 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.util.Calendar;
import java.util.Date;

import net.sf.mpxj.DateRange;
import net.sf.mpxj.Day;
import net.sf.mpxj.DayType;
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.common.DateHelper;

/**
 * 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;
            long duration;
            int periodCount;
            Date start;

            //
            // 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;
               }

               Date fromDate = MPPUtility.getDate(data, offset);
               Date toDate = MPPUtility.getDate(data, offset + 2);
               exception = cal.addCalendarException(fromDate, toDate);

               periodCount = MPPUtility.getShort(data, offset + 14);
               if (periodCount != 0)
               {
                  for (int exceptionPeriodIndex = 0; exceptionPeriodIndex < periodCount; exceptionPeriodIndex++)
                  {
                     start = MPPUtility.getTime(data, offset + 20 + (exceptionPeriodIndex * 2));
                     duration = MPPUtility.getDuration(data, offset + 32 + (exceptionPeriodIndex * 4));
                     exception.addRange(new DateRange(start, new Date(start.getTime() + duration)));
                  }
               }

               //
               // 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));
               }

               //System.out.println(ByteArrayHelper.hexdump(data, offset, 92, false));

               RecurringData rd = new RecurringData();
               int recurrenceTypeValue = MPPUtility.getShort(data, offset + 72);
               rd.setStartDate(exception.getFromDate());
               rd.setFinishDate(exception.getToDate());
               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(Day.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(Day.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;
                  }
               }

               //
               // The default values for a non-recurring exception are daily, with 1 occurrence.
               // Only add recurrence data if it is non-default.
               //
               if (rd.getRecurrenceType() != RecurrenceType.DAILY || rd.getOccurrences().intValue() != 1)
               {
                  exception.setRecurring(rd);
               }

               offset += (92 + exceptionNameLength);
            }
         }

         processWorkWeeks(data, offset, cal);
      }
   }

   /**
    * 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 (Day day : Day.values())
         {
            // 60 byte block per day
            processWorkWeekDay(data, offset, week, day);
            offset += 60;
         }

         Date startDate = DateHelper.getDayStartDate(MPPUtility.getDate(data, offset));
         offset += 2;

         Date finishDate = DateHelper.getDayEndDate(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 DateRange(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, Day day)
   {
      //System.out.println(ByteArrayHelper.hexdump(data, offset, 60, false));

      int dayType = MPPUtility.getShort(data, offset + 0);
      if (dayType == 1)
      {
         week.setWorkingDay(day, DayType.DEFAULT);
      }
      else
      {
         ProjectCalendarHours hours = week.addCalendarHours(day);
         int rangeCount = MPPUtility.getShort(data, offset + 2);
         if (rangeCount == 0)
         {
            week.setWorkingDay(day, DayType.NON_WORKING);
         }
         else
         {
            week.setWorkingDay(day, DayType.WORKING);
            Calendar cal = DateHelper.popCalendar();
            for (int index = 0; index < rangeCount; index++)
            {
               Date startTime = DateHelper.getCanonicalTime(MPPUtility.getTime(data, offset + 8 + (index * 2)));
               int durationInSeconds = MPPUtility.getInt(data, offset + 20 + (index * 4)) * 6;
               cal.setTime(startTime);
               cal.add(Calendar.SECOND, durationInSeconds);
               Date finishTime = DateHelper.getCanonicalTime(cal.getTime());
               hours.addRange(new DateRange(startTime, finishTime));
            }
            DateHelper.pushCalendar(cal);
         }
      }
   }

   /**
    * 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 - 2025 Weber Informatics LLC | Privacy Policy