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

net.sf.mpxj.mpp.TimephasedDataFactory 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.8.0
Show newest version
/*
 * file:       TimephasedDataFactory
 * author:     Jon Iles
 * copyright:  (c) Packwood Software 2008
 * date:       25/10/2008
 */

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

import net.sf.mpxj.Duration;
import net.sf.mpxj.ProjectCalendar;
import net.sf.mpxj.ResourceAssignment;
import net.sf.mpxj.ResourceType;
import net.sf.mpxj.TimeUnit;
import net.sf.mpxj.TimephasedCost;
import net.sf.mpxj.TimephasedCostContainer;
import net.sf.mpxj.TimephasedWork;
import net.sf.mpxj.TimephasedWorkContainer;
import net.sf.mpxj.common.DefaultTimephasedCostContainer;
import net.sf.mpxj.common.DefaultTimephasedWorkContainer;
import net.sf.mpxj.common.NumberHelper;
import net.sf.mpxj.common.TimephasedCostNormaliser;
import net.sf.mpxj.common.TimephasedWorkNormaliser;

/**
 * This class contains methods to create lists of TimephasedWork
 * and TimephasedCost instances.
 */
final class TimephasedDataFactory
{
   /**
    * Given a block of data representing completed work, this method will
    * retrieve a set of TimephasedWork instances which represent
    * the day by day work carried out for a specific resource assignment.
    *
    * @param calendar calendar on which date calculations are based
    * @param resourceAssignment resource assignment
    * @param data completed work data block
    * @return list of TimephasedWork instances
    */
   public List getCompleteWork(ProjectCalendar calendar, ResourceAssignment resourceAssignment, byte[] data)
   {
      List list = new ArrayList<>();

      if (calendar != null && data != null && data.length > 2 && MPPUtility.getShort(data, 0) > 0)
      {
         Date startDate = resourceAssignment.getStart();
         double finishTime = MPPUtility.getInt(data, 24);

         int blockCount = MPPUtility.getShort(data, 0);
         double previousCumulativeWork = 0;
         TimephasedWork previousAssignment = null;

         int index = 32;
         int currentBlock = 0;
         while (currentBlock < blockCount && index + 20 <= data.length)
         {
            double time = MPPUtility.getInt(data, index + 0);

            // If the start of this block is before the start of the assignment, or after the end of the assignment
            // the values don't make sense, so we'll just set the start of this block to be the start of the assignment.
            // This deals with an issue where odd timephased data like this was causing an MPP file to be read
            // extremely slowly.
            if (time < 0 || time > finishTime)
            {
               time = 0;
            }
            else
            {
               time /= 80;
            }
            Duration startWork = Duration.getInstance(time, TimeUnit.MINUTES);

            double currentCumulativeWork = (long) MPPUtility.getDouble(data, index + 4);
            double assignmentDuration = currentCumulativeWork - previousCumulativeWork;
            previousCumulativeWork = currentCumulativeWork;
            assignmentDuration /= 1000;
            Duration totalWork = Duration.getInstance(assignmentDuration, TimeUnit.MINUTES);
            time = (long) MPPUtility.getDouble(data, index + 12);
            time /= 125;
            time *= 6;
            Duration workPerDay = Duration.getInstance(time, TimeUnit.MINUTES);

            Date start;
            if (startWork.getDuration() == 0)
            {
               start = startDate;
            }
            else
            {
               start = calendar.getDate(startDate, startWork, true);
            }

            TimephasedWork assignment = new TimephasedWork();
            assignment.setStart(start);
            assignment.setAmountPerDay(workPerDay);
            assignment.setTotalAmount(totalWork);

            if (previousAssignment != null)
            {
               Date finish = calendar.getDate(startDate, startWork, false);
               previousAssignment.setFinish(finish);
               if (previousAssignment.getStart().getTime() == previousAssignment.getFinish().getTime())
               {
                  list.remove(list.size() - 1);
               }
            }

            list.add(assignment);
            previousAssignment = assignment;

            index += 20;
            ++currentBlock;
         }

         if (previousAssignment != null)
         {
            Duration finishWork = Duration.getInstance(finishTime / 80, TimeUnit.MINUTES);
            Date finish = calendar.getDate(startDate, finishWork, false);
            previousAssignment.setFinish(finish);
            if (previousAssignment.getStart().getTime() == previousAssignment.getFinish().getTime())
            {
               list.remove(list.size() - 1);
            }
         }
      }

      return list;
   }

   /**
    * Given a block of data representing planned work, this method will
    * retrieve a set of TimephasedWork instances which represent
    * the day by day work planned for a specific resource assignment.
    *
    * @param calendar calendar on which date calculations are based
    * @param startDate assignment start date
    * @param units assignment units
    * @param data planned work data block
    * @param timephasedComplete list of complete work
    * @param resourceType resource type
    * @return list of TimephasedWork instances
    */
   public List getPlannedWork(ProjectCalendar calendar, Date startDate, double units, byte[] data, List timephasedComplete, ResourceType resourceType)
   {
      List list = new ArrayList<>();

      if (calendar != null && data != null && data.length > 0)
      {
         int blockCount = MPPUtility.getShort(data, 0);
         if (blockCount == 0)
         {
            if (!timephasedComplete.isEmpty() && units != 0 && data.length >= 24)
            {
               TimephasedWork lastComplete = timephasedComplete.get(timephasedComplete.size() - 1);

               Date startWork = calendar.getNextWorkStart(lastComplete.getFinish());
               double time = MPPUtility.getDouble(data, 16);
               time /= 1000;
               Duration totalWork = Duration.getInstance(time, TimeUnit.MINUTES);
               Duration adjustedTotalWork;
               if (resourceType == ResourceType.WORK)
               {
                  adjustedTotalWork = Duration.getInstance((time * 100) / units, TimeUnit.MINUTES);
               }
               else
               {
                  adjustedTotalWork = Duration.getInstance(time, TimeUnit.MINUTES);
               }
               Date finish = calendar.getDate(startWork, adjustedTotalWork, false);

               time = MPPUtility.getDouble(data, 8);
               time /= 2000;
               time *= 6;
               Duration workPerDay = Duration.getInstance(time, TimeUnit.MINUTES);

               TimephasedWork assignment = new TimephasedWork();
               assignment.setStart(startWork);
               assignment.setAmountPerDay(workPerDay);
               assignment.setModified(false);
               assignment.setFinish(finish);
               assignment.setTotalAmount(totalWork);

               if (assignment.getStart().getTime() != assignment.getFinish().getTime())
               {
                  list.add(assignment);
               }
            }
         }
         else
         {
            Date offset = startDate;

            if (!timephasedComplete.isEmpty())
            {
               TimephasedWork lastComplete = timephasedComplete.get(timephasedComplete.size() - 1);
               offset = lastComplete.getFinish();
            }

            int index = 40;
            double previousCumulativeWork = 0;
            TimephasedWork previousAssignment = null;
            int currentBlock = 0;
            int previousModifiedFlag = 0;

            while (currentBlock < blockCount && index + 28 <= data.length)
            {
               double time = MPPUtility.getInt(data, index);
               time /= 80;
               Duration blockDuration = Duration.getInstance(time, TimeUnit.MINUTES);
               Date start;
               if (blockDuration.getDuration() == 0)
               {
                  start = offset;
               }
               else
               {
                  start = calendar.getDate(offset, blockDuration, true);
               }

               double currentCumulativeWork = MPPUtility.getDouble(data, index + 4);
               double assignmentDuration = currentCumulativeWork - previousCumulativeWork;
               assignmentDuration /= 1000;
               Duration totalWork = Duration.getInstance(assignmentDuration, TimeUnit.MINUTES);
               previousCumulativeWork = currentCumulativeWork;

               time = MPPUtility.getDouble(data, index + 12);
               time /= 2000;
               time *= 6;
               Duration workPerDay = Duration.getInstance(time, TimeUnit.MINUTES);

               int currentModifiedFlag = MPPUtility.getShort(data, index + 22);
               boolean modified = (currentBlock > 0 && previousModifiedFlag != 0 && currentModifiedFlag == 0) || ((currentModifiedFlag & 0x3000) != 0);
               previousModifiedFlag = currentModifiedFlag;

               TimephasedWork assignment = new TimephasedWork();
               assignment.setStart(start);
               assignment.setAmountPerDay(workPerDay);
               assignment.setModified(modified);
               assignment.setTotalAmount(totalWork);

               if (previousAssignment != null)
               {
                  Date finish = calendar.getDate(offset, blockDuration, false);
                  previousAssignment.setFinish(finish);
                  if (previousAssignment.getStart().getTime() == previousAssignment.getFinish().getTime())
                  {
                     list.remove(list.size() - 1);
                  }
               }

               list.add(assignment);
               previousAssignment = assignment;

               index += 28;
               ++currentBlock;
            }

            if (previousAssignment != null)
            {
               double time = MPPUtility.getInt(data, 24);
               time /= 80;
               Duration blockDuration = Duration.getInstance(time, TimeUnit.MINUTES);
               Date finish = calendar.getDate(offset, blockDuration, false);
               previousAssignment.setFinish(finish);
               if (previousAssignment.getStart().getTime() == previousAssignment.getFinish().getTime())
               {
                  list.remove(list.size() - 1);
               }
            }
         }
      }

      return list;
   }

   /**
    * Test the list of TimephasedWork instances to see
    * if any of them have been modified.
    *
    * @param list list of TimephasedWork instances
    * @return boolean flag
    */
   public boolean getWorkModified(List list)
   {
      boolean result = false;
      for (TimephasedWork assignment : list)
      {
         result = assignment.getModified();
         if (result)
         {
            break;
         }
      }
      return result;
   }

   /**
    * Extracts baseline work from the MPP file for a specific baseline.
    * Returns null if no baseline work is present, otherwise returns
    * a list of timephased work items.
    *
    * @param assignment parent assignment
    * @param calendar baseline calendar
    * @param normaliser normaliser associated with this data
    * @param data timephased baseline work data block
    * @param raw flag indicating if this data is to be treated as raw
    * @return timephased work
    */
   public TimephasedWorkContainer getBaselineWork(ResourceAssignment assignment, ProjectCalendar calendar, TimephasedWorkNormaliser normaliser, byte[] data, boolean raw)
   {
      TimephasedWorkContainer result = null;

      if (data != null && data.length > 0)
      {
         List list = null;

         //System.out.println(ByteArrayHelper.hexdump(data, false));
         int index = 8; // 8 byte header
         int blockSize = 40;
         double previousCumulativeWorkPerformedInMinutes = 0;

         Date blockStartDate = MPPUtility.getTimestampFromTenths(data, index + 36);
         index += blockSize;
         TimephasedWork work = null;

         while (index + blockSize <= data.length)
         {
            double cumulativeWorkInMinutes = (double) ((long) MPPUtility.getDouble(data, index + 20)) / 1000;
            if (!Duration.durationValueEquals(cumulativeWorkInMinutes, previousCumulativeWorkPerformedInMinutes))
            {
               //double unknownWorkThisPeriodInMinutes = ((long) MPPUtility.getDouble(data, index + 0)) / 1000;
               double normalActualWorkThisPeriodInMinutes = ((double) MPPUtility.getInt(data, index + 8)) / 10;
               double normalRemainingWorkThisPeriodInMinutes = ((double) MPPUtility.getInt(data, index + 28)) / 10;
               double workThisPeriodInMinutes = cumulativeWorkInMinutes - previousCumulativeWorkPerformedInMinutes;
               double overtimeWorkThisPeriodInMinutes = workThisPeriodInMinutes - (normalActualWorkThisPeriodInMinutes + normalRemainingWorkThisPeriodInMinutes);
               double overtimeFactor = overtimeWorkThisPeriodInMinutes / (normalActualWorkThisPeriodInMinutes + normalRemainingWorkThisPeriodInMinutes);

               double normalWorkPerDayInMinutes = 480;
               double overtimeWorkPerDayInMinutes = normalWorkPerDayInMinutes * overtimeFactor;

               work = new TimephasedWork();
               work.setFinish(MPPUtility.getTimestampFromTenths(data, index + 16));
               work.setStart(blockStartDate);
               work.setTotalAmount(Duration.getInstance(workThisPeriodInMinutes, TimeUnit.MINUTES));
               work.setAmountPerDay(Duration.getInstance(normalWorkPerDayInMinutes + overtimeWorkPerDayInMinutes, TimeUnit.MINUTES));

               previousCumulativeWorkPerformedInMinutes = cumulativeWorkInMinutes;

               if (list == null)
               {
                  list = new ArrayList<>();
               }
               list.add(work);
               //System.out.println(work);
            }
            blockStartDate = MPPUtility.getTimestampFromTenths(data, index + 36);
            index += blockSize;
         }

         if (list != null)
         {
            if (work != null)
            {
               work.setFinish(assignment.getFinish());
            }
            result = new DefaultTimephasedWorkContainer(calendar, normaliser, list, raw);
         }
      }

      return result;
   }

   /**
    * Extracts baseline cost from the MPP file for a specific baseline.
    * Returns null if no baseline cost is present, otherwise returns
    * a list of timephased work items.
    *
    * @param calendar baseline calendar
    * @param normaliser normaliser associated with this data
    * @param data timephased baseline work data block
    * @param raw flag indicating if this data is to be treated as raw
    * @return timephased work
    */
   public TimephasedCostContainer getBaselineCost(ProjectCalendar calendar, TimephasedCostNormaliser normaliser, byte[] data, boolean raw)
   {
      TimephasedCostContainer result = null;

      if (data != null && data.length > 0)
      {
         List list = null;

         //System.out.println(ByteArrayHelper.hexdump(data, false));
         int index = 16; // 16 byte header
         int blockSize = 20;
         double previousTotalCost = 0;

         Date blockStartDate = MPPUtility.getTimestampFromTenths(data, index + 16);
         index += blockSize;

         while (index + blockSize <= data.length)
         {
            Date blockEndDate = MPPUtility.getTimestampFromTenths(data, index + 16);
            double currentTotalCost = (double) ((long) MPPUtility.getDouble(data, index + 8)) / 100;
            if (!costEquals(previousTotalCost, currentTotalCost))
            {
               TimephasedCost cost = new TimephasedCost();
               cost.setStart(blockStartDate);
               cost.setFinish(blockEndDate);
               cost.setTotalAmount(Double.valueOf(currentTotalCost - previousTotalCost));

               if (list == null)
               {
                  list = new ArrayList<>();
               }
               list.add(cost);
               //System.out.println(cost);

               previousTotalCost = currentTotalCost;
            }

            blockStartDate = blockEndDate;
            index += blockSize;
         }

         if (list != null)
         {
            result = new DefaultTimephasedCostContainer(calendar, normaliser, list, raw);
         }
      }

      return result;
   }

   /**
    * Equality test cost values.
    *
    * @param lhs cost value
    * @param rhs cost value
    * @return true if costs are equal, within the allowable delta
    */
   private boolean costEquals(double lhs, double rhs)
   {
      return NumberHelper.equals(lhs, rhs, 0.00001);
   }

}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy