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

net.sf.mpxj.utility.TimephasedUtility 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:       TimephasedUtility.java
 * author:     Jon Iles
 * copyright:  (c) Packwood Software 2011
 * date:       2011-02-12
 */

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

import java.util.ArrayList;
import java.util.Calendar;
import java.util.Date;
import java.util.List;

import net.sf.mpxj.DateRange;
import net.sf.mpxj.Duration;
import net.sf.mpxj.ProjectCalendar;
import net.sf.mpxj.ProjectFile;
import net.sf.mpxj.TimeUnit;
import net.sf.mpxj.TimephasedCost;
import net.sf.mpxj.TimephasedItem;
import net.sf.mpxj.TimephasedWork;
import net.sf.mpxj.common.DateHelper;
import net.sf.mpxj.common.NumberHelper;
import net.sf.mpxj.mpp.TimescaleUnits;

/**
 * This class contains methods relating to manipulating timephased data. 
 */
public final class TimephasedUtility
{
   /**
    * This is the main entry point used to convert the internal representation
    * of timephased work into an external form which can
    * be displayed to the user.
    * 
    * @param projectCalendar calendar used by the resource assignment 
    * @param work timephased resource assignment data
    * @param rangeUnits timescale units
    * @param dateList timescale date ranges
    * @return list of durations, one per timescale date range
    */
   public ArrayList segmentWork(ProjectCalendar projectCalendar, List work, TimescaleUnits rangeUnits, ArrayList dateList)
   {
      ArrayList result = new ArrayList(dateList.size());
      int lastStartIndex = 0;

      //
      // Iterate through the list of dates range we are interested in.
      // Each date range in this list corresponds to a column
      // shown on the "timescale" view by MS Project
      //
      for (DateRange range : dateList)
      {
         //
         // If the current date range does not intersect with any of the
         // assignment date ranges in the list, then we show a zero
         // duration for this date range.
         //
         int startIndex = lastStartIndex == -1 ? -1 : getStartIndex(range, work, lastStartIndex);
         if (startIndex == -1)
         {
            result.add(Duration.getInstance(0, TimeUnit.HOURS));
         }
         else
         {
            //
            // We have found an assignment which intersects with the current 
            // date range, call the method below to determine how
            // much time from this resource assignment can be allocated
            // to the current date range.
            //
            result.add(getRangeDuration(projectCalendar, rangeUnits, range, work, startIndex));
            lastStartIndex = startIndex;
         }
      }

      return result;
   }

   /**
    * This is the main entry point used to convert the internal representation
    * of timephased baseline work into an external form which can
    * be displayed to the user.
    *  
    * @param file parent project file
    * @param work timephased resource assignment data
    * @param rangeUnits timescale units
    * @param dateList timescale date ranges
    * @return list of durations, one per timescale date range
    */
   public ArrayList segmentBaselineWork(ProjectFile file, List work, TimescaleUnits rangeUnits, ArrayList dateList)
   {
      return segmentWork(file.getBaselineCalendar(), work, rangeUnits, dateList);
   }

   /**
    * This is the main entry point used to convert the internal representation
    * of timephased cost into an external form which can
    * be displayed to the user.
    * 
    * @param projectCalendar calendar used by the resource assignment 
    * @param cost timephased resource assignment data
    * @param rangeUnits timescale units
    * @param dateList timescale date ranges
    * @return list of durations, one per timescale date range
    */
   public ArrayList segmentCost(ProjectCalendar projectCalendar, List cost, TimescaleUnits rangeUnits, ArrayList dateList)
   {
      ArrayList result = new ArrayList(dateList.size());
      int lastStartIndex = 0;

      //
      // Iterate through the list of dates range we are interested in.
      // Each date range in this list corresponds to a column
      // shown on the "timescale" view by MS Project
      //
      for (DateRange range : dateList)
      {
         //
         // If the current date range does not intersect with any of the
         // assignment date ranges in the list, then we show a zero
         // duration for this date range.
         //
         int startIndex = lastStartIndex == -1 ? -1 : getStartIndex(range, cost, lastStartIndex);
         if (startIndex == -1)
         {
            result.add(NumberHelper.DOUBLE_ZERO);
         }
         else
         {
            //
            // We have found an assignment which intersects with the current 
            // date range, call the method below to determine how
            // much time from this resource assignment can be allocated
            // to the current date range.
            //
            result.add(getRangeCost(projectCalendar, rangeUnits, range, cost, startIndex));
            lastStartIndex = startIndex;
         }
      }

      return result;
   }

   /**
    * This is the main entry point used to convert the internal representation
    * of timephased baseline cost into an external form which can
    * be displayed to the user.
    *  
    * @param file parent project file 
    * @param cost timephased resource assignment data
    * @param rangeUnits timescale units
    * @param dateList timescale date ranges
    * @return list of durations, one per timescale date range
    */
   public ArrayList segmentBaselineCost(ProjectFile file, List cost, TimescaleUnits rangeUnits, ArrayList dateList)
   {
      return segmentCost(file.getBaselineCalendar(), cost, rangeUnits, dateList);
   }

   /**
    * Used to locate the first timephased resource assignment block which
    * intersects with the target date range.
    * 
    * @param  payload type
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start the search
    * @return index of timephased resource assignment which intersects with the target date range
    */
   private > int getStartIndex(DateRange range, List assignments, int startIndex)
   {
      int result = -1;
      if (assignments != null)
      {
         long rangeStart = range.getStart().getTime();
         long rangeEnd = range.getEnd().getTime();

         for (int loop = startIndex; loop < assignments.size(); loop++)
         {
            T assignment = assignments.get(loop);
            int compareResult = DateHelper.compare(assignment.getStart(), assignment.getFinish(), rangeStart);

            //
            // The start of the target range falls after the assignment end - 
            // move on to test the next assignment.
            //
            if (compareResult > 0)
            {
               continue;
            }

            //
            // The start of the target range  falls within the assignment -
            // return the index of this assignment to the caller.
            //
            if (compareResult == 0)
            {
               result = loop;
               break;
            }

            //
            // At this point, we know that the start of the target range is before
            // the assignment start. We need to determine if the end of the
            // target range overlaps the assignment.
            //
            compareResult = DateHelper.compare(assignment.getStart(), assignment.getFinish(), rangeEnd);
            if (compareResult >= 0)
            {
               result = loop;
               break;
            }
         }
      }
      return result;
   }

   /**
    * For a given date range, determine the duration of work, based on the
    * timephased resource assignment data.
    * 
    * @param projectCalendar calendar used for the resource assignment calendar
    * @param rangeUnits timescale units
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start searching through the timephased resource assignments
    * @return work duration
    */
   private Duration getRangeDuration(ProjectCalendar projectCalendar, TimescaleUnits rangeUnits, DateRange range, List assignments, int startIndex)
   {
      Duration result;

      switch (rangeUnits)
      {
         case MINUTES:
         case HOURS:
         {
            result = getRangeDurationSubDay(projectCalendar, rangeUnits, range, assignments, startIndex);
            break;
         }

         default:
         {
            result = getRangeDurationWholeDay(projectCalendar, rangeUnits, range, assignments, startIndex);
            break;
         }
      }

      return result;
   }

   /**
    * For a given date range, determine the duration of work, based on the
    * timephased resource assignment data.
    * 
    * This method deals with timescale units of less than a day.
    * 
    * @param projectCalendar calendar used for the resource assignment calendar
    * @param rangeUnits timescale units
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start searching through the timephased resource assignments
    * @return work duration
    */
   private Duration getRangeDurationSubDay(ProjectCalendar projectCalendar, TimescaleUnits rangeUnits, DateRange range, List assignments, int startIndex)
   {
      throw new UnsupportedOperationException("Please request this functionality from the MPXJ maintainer");
   }

   /**
    * For a given date range, determine the duration of work, based on the
    * timephased resource assignment data.
    *
    * This method deals with timescale units of one day or more.
    * 
    * @param projectCalendar calendar used for the resource assignment calendar
    * @param rangeUnits timescale units
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start searching through the timephased resource assignments
    * @return work duration
    */
   private Duration getRangeDurationWholeDay(ProjectCalendar projectCalendar, TimescaleUnits rangeUnits, DateRange range, List assignments, int startIndex)
   {
      // option 1:
      // Our date range starts before the start of the TRA at the start index.
      // We can guarantee that we don't need to look at any earlier TRA blocks so just start here

      // option 2:
      // Our date range starts at the same point as the first TRA: do nothing...

      // option 3:
      // Our date range starts somewhere inside the first TRA...

      // if it's option 1 just set the start date to the start of the TRA block
      // for everything else we just use the start date of our date range.
      // start counting forwards one day at a time until we reach the end of
      // the date range, or until we reach the end of the block.

      // if we have not reached the end of the range, move to the next block and 
      // see if the date range overlaps it. if it does not overlap, then we're 
      // done.

      // if it does overlap, then move to the next block and repeat

      int totalDays = 0;
      double totalWork = 0;
      TimephasedWork assignment = assignments.get(startIndex);
      boolean done = false;

      do
      {
         //
         // Select the correct start date
         //      
         long startDate = range.getStart().getTime();
         long assignmentStart = assignment.getStart().getTime();
         if (startDate < assignmentStart)
         {
            startDate = assignmentStart;
         }

         long rangeEndDate = range.getEnd().getTime();
         long traEndDate = assignment.getFinish().getTime();

         Calendar cal = Calendar.getInstance();
         cal.setTimeInMillis(startDate);
         Date calendarDate = cal.getTime();

         //
         // Start counting forwards
         //      
         while (startDate < rangeEndDate && startDate < traEndDate)
         {
            if (projectCalendar == null || projectCalendar.isWorkingDate(calendarDate))
            {
               ++totalDays;
            }
            cal.add(Calendar.DAY_OF_YEAR, 1);
            startDate = cal.getTimeInMillis();
            calendarDate = cal.getTime();
         }

         //
         // If we still haven't reached the end of our range
         // check to see if the next TRA can be used.
         //
         done = true;
         totalWork += (assignment.getAmountPerDay().getDuration() * totalDays);
         if (startDate < rangeEndDate)
         {
            ++startIndex;
            if (startIndex < assignments.size())
            {
               assignment = assignments.get(startIndex);
               totalDays = 0;
               done = false;
            }
         }
      }
      while (!done);

      return Duration.getInstance(totalWork, assignment.getAmountPerDay().getUnits());
   }

   /**
    * For a given date range, determine the cost, based on the
    * timephased resource assignment data.
    * 
    * @param projectCalendar calendar used for the resource assignment calendar
    * @param rangeUnits timescale units
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start searching through the timephased resource assignments
    * @return work duration
    */
   private Double getRangeCost(ProjectCalendar projectCalendar, TimescaleUnits rangeUnits, DateRange range, List assignments, int startIndex)
   {
      Double result;

      switch (rangeUnits)
      {
         case MINUTES:
         case HOURS:
         {
            result = getRangeCostSubDay(projectCalendar, rangeUnits, range, assignments, startIndex);
            break;
         }

         default:
         {
            result = getRangeCostWholeDay(projectCalendar, rangeUnits, range, assignments, startIndex);
            break;
         }
      }

      return result;
   }

   /**
    * For a given date range, determine the cost, based on the
    * timephased resource assignment data.
    *
    * This method deals with timescale units of one day or more.
    * 
    * @param projectCalendar calendar used for the resource assignment calendar
    * @param rangeUnits timescale units
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start searching through the timephased resource assignments
    * @return work duration
    */
   private Double getRangeCostWholeDay(ProjectCalendar projectCalendar, TimescaleUnits rangeUnits, DateRange range, List assignments, int startIndex)
   {
      int totalDays = 0;
      double totalCost = 0;
      TimephasedCost assignment = assignments.get(startIndex);
      boolean done = false;

      do
      {
         //
         // Select the correct start date
         //      
         long startDate = range.getStart().getTime();
         long assignmentStart = assignment.getStart().getTime();
         if (startDate < assignmentStart)
         {
            startDate = assignmentStart;
         }

         long rangeEndDate = range.getEnd().getTime();
         long traEndDate = assignment.getFinish().getTime();

         Calendar cal = Calendar.getInstance();
         cal.setTimeInMillis(startDate);
         Date calendarDate = cal.getTime();

         //
         // Start counting forwards
         //      
         while (startDate < rangeEndDate && startDate < traEndDate)
         {
            if (projectCalendar == null || projectCalendar.isWorkingDate(calendarDate))
            {
               ++totalDays;
            }
            cal.add(Calendar.DAY_OF_YEAR, 1);
            startDate = cal.getTimeInMillis();
            calendarDate = cal.getTime();
         }

         //
         // If we still haven't reached the end of our range
         // check to see if the next TRA can be used.
         //
         done = true;
         totalCost += (assignment.getAmountPerDay().doubleValue() * totalDays);
         if (startDate < rangeEndDate)
         {
            ++startIndex;
            if (startIndex < assignments.size())
            {
               assignment = assignments.get(startIndex);
               totalDays = 0;
               done = false;
            }
         }
      }
      while (!done);

      return Double.valueOf(totalCost);
   }

   /**
    * For a given date range, determine the cost, based on the
    * timephased resource assignment data.
    * 
    * This method deals with timescale units of less than a day.
    * 
    * @param projectCalendar calendar used for the resource assignment calendar
    * @param rangeUnits timescale units
    * @param range target date range
    * @param assignments timephased resource assignments
    * @param startIndex index at which to start searching through the timephased resource assignments
    * @return work duration
    */
   private Double getRangeCostSubDay(ProjectCalendar projectCalendar, TimescaleUnits rangeUnits, DateRange range, List assignments, int startIndex)
   {
      throw new UnsupportedOperationException("Please request this functionality from the MPXJ maintainer");
   }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy