/////////////////////////////////////////////////////////////////////////////
//
// 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.BigDecimal;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.projectforge.common.DateHolder;
import org.projectforge.common.Matcher;
import org.projectforge.common.NumberHelper;
public class GanttTaskImpl implements GanttTask, Serializable
{
private static final org.apache.log4j.Logger log = org.apache.log4j.Logger.getLogger(GanttTaskImpl.class);
private static final long serialVersionUID = -2948691380516113581L;
/**
* Maximum supported depth (independent from any display algorithm). This value is only used for the detection of cyclic references!
*/
public static int MAX_DEPTH = 50;
private Integer id;
private Integer predecessorOffset;
private GanttRelationType relationType;
private GanttTask predecessor;
private String description;
private BigDecimal duration;
private Date endDate;
private Date startDate;
private Integer progress;
private String title;
private GanttObjectType type;
private String workpackageCode;
private List<GanttTask> children;
private boolean visible = false;
private transient Date calculatedStartDate;
private transient boolean startDateCalculated;
private transient Date calculatedEndDate;
private transient boolean endDateCalculated;
public GanttTaskImpl()
{
}
public GanttTaskImpl(final Integer id)
{
this.id = id;
}
/**
* @see org.projectforge.gantt.GanttTask#getId()
*/
public Serializable getId()
{
return this.id;
}
public GanttTaskImpl setId(final Serializable id)
{
this.id = (Integer) id;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getPredecessorOffset()
*/
public Integer getPredecessorOffset()
{
return predecessorOffset;
}
public GanttTaskImpl setPredecessorOffset(final Integer predecessorOffset)
{
this.predecessorOffset = predecessorOffset;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getRelationType()
*/
public GanttRelationType getRelationType()
{
return relationType;
}
public GanttTaskImpl setRelationType(GanttRelationType relationType)
{
this.relationType = relationType;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getPredecessor()
*/
public GanttTask getPredecessor()
{
return predecessor;
}
public GanttTaskImpl setPredecessor(GanttTask predecessor)
{
this.predecessor = predecessor;
return this;
}
public Serializable getPredecessorId()
{
if (this.predecessor == null) {
return null;
}
return this.predecessor.getId();
}
/**
* @see org.projectforge.gantt.GanttTask#getDescription()
*/
public String getDescription()
{
return description;
}
public GanttTaskImpl setDescription(String description)
{
this.description = description;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getDuration()
*/
public BigDecimal getDuration()
{
return duration;
}
public GanttTaskImpl setDuration(final BigDecimal duration)
{
this.duration = duration;
this.startDateCalculated = this.endDateCalculated = false; // Force recalculation (also taking the children into account)
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getEndDate()
*/
public Date getEndDate()
{
return endDate;
}
public GanttTaskImpl setEndDate(Date endDate)
{
this.endDate = endDate;
this.startDateCalculated = this.endDateCalculated = false; // Force recalculation (also taking the children into account)
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getStartDate()
*/
public Date getStartDate()
{
return startDate;
}
public GanttTaskImpl setStartDate(Date startDate)
{
this.startDate = startDate;
this.startDateCalculated = this.endDateCalculated = false; // Force recalculation (also taking the children into account)
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getTitle()
*/
public String getTitle()
{
return title;
}
public GanttTaskImpl setTitle(String title)
{
this.title = title;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getType()
*/
public GanttObjectType getType()
{
return type;
}
public GanttTaskImpl setType(GanttObjectType type)
{
this.type = type;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getWorkpackageCode()
*/
public String getWorkpackageCode()
{
return workpackageCode;
}
public GanttTaskImpl setWorkpackageCode(String workpackageCode)
{
this.workpackageCode = workpackageCode;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getChildren()
*/
public List<GanttTask> getChildren()
{
return this.children;
}
/**
* Sort all children by calculated start date.
* @see org.projectforge.gantt.GanttTask#sortChildren()
*/
public void sortChildren()
{
if (this.children == null) {
return;
}
Collections.sort(this.children, GanttUtils.GANTT_OBJECT_COMPARATOR);
for (final GanttTask child : this.children) {
child.sortChildren();
}
}
/**
*
* @see org.projectforge.gantt.GanttTask#addChild(org.projectforge.gantt.GanttTask)
*/
public GanttTaskImpl addChild(final GanttTask child)
{
if (this.children == null) {
this.children = new ArrayList<GanttTask>();
}
this.children.add(child);
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#removeChild(org.projectforge.gantt.GanttTask)
*/
@Override
public void removeChild(final GanttTask ganttObject)
{
if (this.children == null) {
log.error("Can't remove child object because current Gantt activity has no children: " + this);
} else if (this.children.remove(ganttObject) == false) {
log.error("Can't remove child object: " + ganttObject + " because it's not a child of the given activity: " + this);
}
}
/**
* @see org.projectforge.gantt.GanttTask#hasDuration()
*/
public boolean hasDuration()
{
if (getCalculatedStartDate() != null && getCalculatedEndDate() != null) {
final DateHolder dh = new DateHolder(this.calculatedStartDate);
return dh.isSameDay(getCalculatedEndDate()) == false;
}
return !NumberHelper.isZeroOrNull(this.duration);
}
/**
* @see org.projectforge.gantt.GanttTask#getCalculatedStartDate()
*/
public Date getCalculatedStartDate()
{
if (startDateCalculated == false) {
calculatedStartDate = GanttUtils.getCalculatedStartDate(this);
startDateCalculated = true;
}
return this.calculatedStartDate;
}
/**
* @see org.projectforge.gantt.GanttTask#setCalculatedStartDate(java.util.Date)
*/
@Override
public GanttTaskImpl setCalculatedStartDate(Date calculatedStartDate)
{
this.calculatedStartDate = calculatedStartDate;
return this;
}
/**
*
* @see org.projectforge.gantt.GanttTask#getCalculatedEndDate()
*/
public Date getCalculatedEndDate()
{
if (endDateCalculated == false) {
calculatedEndDate = GanttUtils.getCalculatedEndDate(this);
endDateCalculated = true;
}
return this.calculatedEndDate;
}
/**
* @see org.projectforge.gantt.GanttTask#setCalculatedEndDate(java.util.Date)
*/
@Override
public GanttTaskImpl setCalculatedEndDate(Date calculatedEndDate)
{
this.calculatedEndDate = calculatedEndDate;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#isStartDateCalculated()
*/
@Override
public boolean isStartDateCalculated()
{
return this.startDateCalculated == true;
}
/**
* @see org.projectforge.gantt.GanttTask#setStartDateCalculated(boolean)
*/
@Override
public GanttTaskImpl setStartDateCalculated(boolean startDateCalculated)
{
this.startDateCalculated = startDateCalculated;
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#isEndDateCalculated()
*/
@Override
public boolean isEndDateCalculated()
{
return this.endDateCalculated;
}
/**
* @see org.projectforge.gantt.GanttTask#setEndDateCalculated(boolean)
*/
@Override
public GanttTaskImpl setEndDateCalculated(boolean isEndDateCalculated)
{
this.endDateCalculated = isEndDateCalculated;
return this;
}
/**
* Default: true.
* @see org.projectforge.gantt.GanttTask#isVisible()
*/
public boolean isVisible()
{
return this.visible;
}
/**
* @see org.projectforge.gantt.GanttTask#setVisible(boolean)
*/
public GanttTask setVisible(boolean visible)
{
this.visible = visible;
return this;
}
/**
* Sets task visibility and the visibility of all descendants to false.
*/
public GanttTaskImpl setInvisible()
{
this.setVisible(false);
if (this.children != null) {
for (GanttTask child : children) {
((GanttTaskImpl) child).setInvisible();
}
}
return this;
}
/**
* @see org.projectforge.gantt.GanttTask#getProgress()
*/
public Integer getProgress()
{
return progress;
}
public GanttTaskImpl setProgress(Integer progress)
{
this.progress = progress;
return this;
}
/**
* Sets the calculated start and end dates (including the children) to null (recalculation is forced when calculated time will be needed). <br/>
* Please note: The calculation is only done at the first usage.
*/
public GanttTaskImpl recalculate()
{
this.calculatedStartDate = this.calculatedEndDate = null;
this.startDateCalculated = this.endDateCalculated = false;
if (this.children != null) {
for (final GanttTask child : this.children) {
((GanttTaskImpl) child).recalculate();
}
}
return this;
}
/**
* Traverses through the child trees.
* @return true, if no cyclic references are found (everything seems to be OK), otherwise false.
*/
public boolean checkCyclicReferences()
{
return checkCyclicReferences(0);
}
private boolean checkCyclicReferences(final int depth)
{
if (depth > MAX_DEPTH) {
// Maximum of allowed depth exceeded.
return false;
}
if (this.children == null) {
return true;
}
for (final GanttTask child : this.children) {
if (((GanttTaskImpl) child).checkCyclicReferences(depth + 1) == false) {
return false;
}
}
return true;
}
@Override
public String toString()
{
final ToStringBuilder tos = new ToStringBuilder(this);
tos.append("id", getId());
tos.append("title", getTitle());
if (getChildren() != null) {
tos.append("children", getChildren());
}
return tos.toString();
}
public GanttTask findBy(final Matcher<GanttTask> matcher, final Object expression)
{
if (matcher.match(this, expression) == true) {
return this;
}
if (this.children != null) {
for (final GanttTask child : this.children) {
final GanttTask found = child.findBy(matcher, expression);
if (found != null) {
return found;
}
}
}
return null;
}
public GanttTask findById(final Serializable id)
{
return findBy(new Matcher<GanttTask>() {
public boolean match(GanttTask object, Object expression)
{
return (object.getId() != null && object.getId().equals(expression) == true);
}
}, id);
}
public GanttTask findByTitle(final String title)
{
return findBy(new Matcher<GanttTask>() {
public boolean match(GanttTask object, Object expression)
{
return (StringUtils.equals(object.getTitle(), (String) expression) == true);
}
}, title);
}
public GanttTask findByWorkpackageCode(String workpackageCode)
{
return findBy(new Matcher<GanttTask>() {
public boolean match(GanttTask object, Object expression)
{
return (StringUtils.equals(object.getWorkpackageCode(), (String) expression) == true);
}
}, workpackageCode);
}
public GanttTask findParent(final Serializable id)
{
return findBy(new Matcher<GanttTask>() {
public boolean match(GanttTask object, Object expression)
{
if (CollectionUtils.isEmpty(object.getChildren()) == true) {
return false;
}
for (final GanttTask child : object.getChildren()) {
if (child.getId() != null && child.getId().equals(expression) == true) {
return true;
}
}
return false;
}
}, id);
}
/**
* Get the next free id for new Gantt tasks to insert (starting with -1). Positive values are reserved for Gantt tasks related to tasks
* (of ProjectForge's TaskTree) (the id is equals to the task id).
* @return
*/
public int getNextId()
{
final Integer id = getNextId(this, -1);
return id != null ? id : -1;
}
private Integer getNextId(final GanttTask node, final int id)
{
Integer result = null;
if (node == null) {
return null;
}
if (node.getId() != null && ((Integer) node.getId()) <= id) {
result = ((Integer) node.getId()) - 1;
}
final List<GanttTask> children = node.getChildren();
if (children == null) {
return result;
}
for (final GanttTask child : children) {
final Integer i = getNextId(child, id);
if (i == null) {
continue;
}
if (result != null && i <= result || result == null && i <= id) {
result = i;
}
}
return result;
}
}