/////////////////////////////////////////////////////////////////////////////
//
// Project ProjectForge Community Edition
// www.projectforge.org
//
// Copyright (C) 2001-2014 Kai Reinhard (k.reinhard@micromata.de)
//
// ProjectForge is dual-licensed.
//
// This community edition is free software; you can redistribute it and/or
// modify it under the terms of the GNU General Public License as published
// by the Free Software Foundation; version 3 of the License.
//
// This community edition 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 General
// Public License for more details.
//
// You should have received a copy of the GNU General Public License along
// with this program; if not, see http://www.gnu.org/licenses/.
//
/////////////////////////////////////////////////////////////////////////////
package org.projectforge.gantt;
import java.io.Serializable;
import java.math.RoundingMode;
import java.util.Comparator;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import org.apache.commons.lang.ObjectUtils;
import org.apache.commons.lang.StringUtils;
import org.projectforge.common.DateHolder;
import org.projectforge.common.NumberHelper;
import org.projectforge.common.StringHelper;
public class GanttUtils
{
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(GanttUtils.class);
public static Comparator<GanttTask> GANTT_OBJECT_COMPARATOR = new Comparator<GanttTask>() {
public int compare(final GanttTask o1, final GanttTask o2)
{
if (ObjectUtils.equals(o1.getId(), o2.getId()) == true) {
return 0;
}
final Date start1 = o1.getCalculatedStartDate();
final Date start2 = o2.getCalculatedStartDate();
if (start1 == null) {
if (start2 != null) {
return 1;
}
} else if (start2 == null) {
return -1;
} else {
final int result = start1.compareTo(start2);
if (result != 0) {
return result;
}
}
final Date end1 = o1.getCalculatedEndDate();
final Date end2 = o2.getCalculatedEndDate();
if (end1 == null) {
if (end2 != null) {
return 1;
}
} else if (end2 == null) {
return -1;
} else {
final int result = end1.compareTo(end2);
if (result != 0) {
return result;
}
}
if (StringUtils.equals(o1.getTitle(), o2.getTitle()) == false) {
return StringHelper.compareTo(o1.getTitle(), o2.getTitle());
}
return StringHelper.compareTo(String.valueOf(o1.getId()), String.valueOf(o2.getId()));
}
};
/**
* Please note: If the start date is after the start date of the earliest task of any child then the start date of this child is returned.
* Otherwise the set start date or if not set the calculated start date is returned.
* @param node
*/
public static Date getCalculatedStartDate(final GanttTask node)
{
final Date start = getCalculatedStartDate(node, new HashSet<Serializable>(), new HashSet<Serializable>());
return start;
}
private static Date getCalculatedStartDate(final GanttTask node, final Set<Serializable> startDateSet, final Set<Serializable> endDateSet)
{
if (node == null) {
return null;
}
if (node.getStartDate() != null) {
return node.getStartDate();
}
if (node.isStartDateCalculated() == true) {
return node.getCalculatedStartDate();
}
final int durationDays = node.getDuration() != null ? node.getDuration().setScale(0, RoundingMode.HALF_UP).intValue() : 0;
if (node.getDuration() != null && node.getEndDate() != null) {
final Date startDate = calculateDate(node.getEndDate(), -durationDays);
node.setCalculatedStartDate(startDate).setStartDateCalculated(true);
if (log.isDebugEnabled() == true) {
log.debug("calculated start date=" + startDate + " for: " + node);
}
return startDate;
}
if (startDateSet.contains(node.getId()) == true) {
log.error("Circular reference detection (couldn't calculate start date: " + node);
return null;
} else {
startDateSet.add(node.getId());
}
Date startDate = null;
final GanttTask predecessor = node.getPredecessor();
if (predecessor != null) {
startDate = getPredecessorRelDate(node.getRelationType(), predecessor, startDateSet, endDateSet);
if (startDate != null) {
if (NumberHelper.isNotZero(node.getPredecessorOffset()) == true) {
startDate = calculateDate(startDate, node.getPredecessorOffset());
}
if (node.getRelationType() == GanttRelationType.START_FINISH || node.getRelationType() == GanttRelationType.FINISH_FINISH) {
if (durationDays > 0) {
startDate = calculateDate(startDate, -durationDays);
}
}
}
}
if ((predecessor == null || (node.getRelationType() != null && node.getRelationType().isIn(GanttRelationType.FINISH_FINISH,
GanttRelationType.START_FINISH) == true))
&& node.getChildren() != null) {
// Calculate start date from the earliest child.
for (final GanttTask child : node.getChildren()) {
final Date date = getCalculatedStartDate(child, startDateSet, endDateSet);
if (startDate == null) {
startDate = date;
} else if (date != null && date.before(startDate) == true) {
if (log.isDebugEnabled() == true) {
log.debug("Start date of child is before start date=" + date + " of parent: " + child);
}
startDate = date;
}
}
}
if (startDate == null && node.getDuration() != null) {
final Date calculatedEndDate = getCalculatedEndDate(node, startDateSet, endDateSet);
if (calculatedEndDate != null) {
startDate = calculateDate(calculatedEndDate, -durationDays);
}
}
node.setCalculatedStartDate(startDate).setStartDateCalculated(true);
if (log.isDebugEnabled() == true) {
log.debug("calculated start date=" + startDate + " for: " + node);
}
return startDate;
}
private static Date getPredecessorRelDate(final GanttRelationType relationType, final GanttTask predecessor,
final Set<Serializable> startDateSet, final Set<Serializable> endDateSet)
{
if (relationType == GanttRelationType.START_START || relationType == GanttRelationType.START_FINISH) {
final Date calculatedStartDate = getCalculatedStartDate(predecessor, startDateSet, endDateSet);
return calculatedStartDate;
} else {
final Date calculatedEndDate = getCalculatedEndDate(predecessor, startDateSet, endDateSet);
return calculatedEndDate;
}
}
/**
* Calculates the end date. If the end date is set then this value is returned (ignoring the duration in days). If the end date is not
* given, then the start date is taken and durationDays (only working days) will be added. If no start date is given, the start date will
* be calculated from the node this node depends on.
*/
public static Date getCalculatedEndDate(final GanttTask node)
{
final Date end = getCalculatedEndDate(node, new HashSet<Serializable>(), new HashSet<Serializable>());
return end;
}
/**
* @param node
* @param depth For avoiding stack overflow errors
* @return
*/
private static Date getCalculatedEndDate(final GanttTask node, final Set<Serializable> startDateSet, final Set<Serializable> endDateSet)
{
if (node == null) {
return null;
}
if (node.getEndDate() != null) {
return node.getEndDate();
}
if (node.isEndDateCalculated() == true) {
return node.getCalculatedEndDate();
}
final int durationDays = node.getDuration() != null ? node.getDuration().setScale(0, RoundingMode.HALF_UP).intValue() : 0;
if (node.getDuration() != null && node.getStartDate() != null) {
final Date endDate = calculateDate(node.getStartDate(), durationDays);
node.setCalculatedEndDate(endDate).setEndDateCalculated(true);
if (log.isDebugEnabled() == true) {
log.debug("calculated end date=" + endDate + " for: " + node);
}
return endDate;
}
if (endDateSet.contains(node.getId()) == true) {
log.error("Circular reference detection (couldn't calculate end date: " + node);
return null;
} else {
endDateSet.add(node.getId());
}
Date endDate = null;
final GanttTask predecessor = node.getPredecessor();
if (predecessor != null) {
endDate = getPredecessorRelDate(node.getRelationType(), predecessor, startDateSet, endDateSet);
if (endDate != null) {
if (NumberHelper.isNotZero(node.getPredecessorOffset()) == true) {
endDate = calculateDate(endDate, node.getPredecessorOffset());
}
if (node.getRelationType() != GanttRelationType.START_FINISH && node.getRelationType() != GanttRelationType.FINISH_FINISH) {
if (durationDays > 0) {
endDate = calculateDate(endDate, durationDays);
}
}
}
}
if ((predecessor == null || (node.getRelationType() == null || node.getRelationType().isIn(GanttRelationType.FINISH_FINISH,
GanttRelationType.START_FINISH) == false))
&& node.getChildren() != null
&& node.getDuration() == null) {
// There are children and the end date is not fix defined by a predecessor.
for (final GanttTask child : node.getChildren()) {
final Date date = getCalculatedEndDate(child, startDateSet, endDateSet);
if (date != null && (endDate == null || date.after(endDate)) == true) {
endDate = date;
}
}
}
if (endDate == null && node.getDuration() != null) {
final Date calculatedStartDate = getCalculatedStartDate(node, startDateSet, endDateSet);
if (calculatedStartDate != null) {
endDate = calculateDate(calculatedStartDate, durationDays);
}
}
node.setCalculatedEndDate(endDate).setEndDateCalculated(true);
if (log.isDebugEnabled() == true) {
log.debug("calculated end date=" + endDate + " for: " + node);
}
return endDate;
}
private static Date calculateDate(final Date date, final int workingDayOffset)
{
final DateHolder dh = new DateHolder(date);
dh.addWorkingDays(workingDayOffset);
return dh.getDate();
}
}