/*
* 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.Date;
import java.util.LinkedList;
import java.util.List;
import net.sf.mpxj.Duration;
import net.sf.mpxj.ProjectCalendar;
import net.sf.mpxj.ResourceAssignment;
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 startDate assignment start date
* @param data completed work data block
* @return list of TimephasedWork instances
*/
public List<TimephasedWork> getCompleteWork(ProjectCalendar calendar, Date startDate, byte[] data)
{
LinkedList<TimephasedWork> list = new LinkedList<TimephasedWork>();
if (calendar != null && data != null && data.length > 0)
{
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);
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.removeLast();
}
}
list.add(assignment);
previousAssignment = assignment;
index += 20;
++currentBlock;
}
if (previousAssignment != null)
{
double time = MPPUtility.getInt(data, 24);
time /= 80;
Duration finishWork = Duration.getInstance(time, TimeUnit.MINUTES);
Date finish = calendar.getDate(startDate, finishWork, false);
previousAssignment.setFinish(finish);
if (previousAssignment.getStart().getTime() == previousAssignment.getFinish().getTime())
{
list.removeLast();
}
}
}
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
* @return list of TimephasedWork instances
*/
public List<TimephasedWork> getPlannedWork(ProjectCalendar calendar, Date startDate, double units, byte[] data, List<TimephasedWork> timephasedComplete)
{
LinkedList<TimephasedWork> list = new LinkedList<TimephasedWork>();
if (calendar != null && data != null && data.length > 0)
{
int blockCount = MPPUtility.getShort(data, 0);
if (blockCount == 0)
{
if (!timephasedComplete.isEmpty() && units != 0)
{
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 = Duration.getInstance((time * 100) / units, 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;
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 modifiedFlag = MPPUtility.getShort(data, index + 22);
boolean modified = (modifiedFlag == 0 && currentBlock != 0) || ((modifiedFlag & 0x3000) != 0);
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.removeLast();
}
}
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.removeLast();
}
}
}
}
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<TimephasedWork> 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)
{
LinkedList<TimephasedWork> list = null;
//System.out.println(MPPUtility.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 LinkedList<TimephasedWork>();
}
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)
{
LinkedList<TimephasedCost> list = null;
//System.out.println(MPPUtility.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 LinkedList<TimephasedCost>();
}
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);
}
}