/////////////////////////////////////////////////////////////////////////////
//
// 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.plugins.skillmatrix;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.List;
import org.apache.commons.lang.builder.ToStringBuilder;
import org.apache.log4j.Logger;
import org.projectforge.core.IdObject;
import org.projectforge.core.UserException;
/**
* Represents a single skill as part of the SkillTree. The data of a skill node is stored in the database.
* @author Billy Duong (b.duong@micromata.de)
*/
public class SkillNode implements IdObject<Integer>, Serializable
{
private static final Logger log = Logger.getLogger(SkillNode.class);
private static final long serialVersionUID = 8016231590126097371L;
/** Reference to the parent skill node with the parentSkillId. */
SkillNode parent = null;
/**
* References to all child nodes.
*/
List<SkillNode> childs = null;
/** The data of this SkillNode. */
SkillDO skill = null;
public SkillNode()
{
}
/**
* @return True, if the parent skill id of the underlying skill is null, false otherwise.
*/
public boolean isRootNode()
{
return this.skill.getParentId() == null;
}
public void setSkill(final SkillDO skill)
{
this.skill = skill;
}
public SkillDO getSkill()
{
return skill;
}
/** The id of this skill given by the database. */
public Integer getId()
{
return skill.getId();
}
public Integer getParentId()
{
if (parent == null) {
return null;
}
return parent.getId();
}
/** Returns the parent skill. */
public SkillNode getParent()
{
return this.parent;
}
public void internalSetParent(final SkillNode parent)
{
this.parent = parent;
if (parent != null) {
parent.addChild(this);
}
}
/**
* Returns all childs of this skill.
*/
public List<SkillNode> getChilds()
{
return this.childs;
}
/** Does this skill has any childs? */
public boolean hasChilds()
{
return this.childs != null && this.childs.size() > 0 ? true : false;
}
/** Checks if the given node is a child / descendant of this node. */
public boolean isParentOf(final SkillNode node)
{
if (this.childs == null) {
return false;
}
for (final SkillNode child : this.childs) {
if (child.equals(node) == true) {
return true;
} else if (child.isParentOf(node) == true) {
return true;
}
}
return false;
}
/**
* Returns the path to the root node in an ArrayList. The list contains also the current skill.
*/
public List<SkillNode> getPathToRoot()
{
return getPathToAncestor(null);
}
/**
* Returns the path to the parent node in an ArrayList.
*/
public List<SkillNode> getPathToAncestor(final Integer ancestorSkillId)
{
if (this.parent == null || this.getId().equals(ancestorSkillId) == true) {
return new ArrayList<SkillNode>();
}
final List<SkillNode> path = this.parent.getPathToAncestor(ancestorSkillId);
path.add(this);
return path;
}
/**
* Sets / changes the parent of this node. This method does not modify the parent skill! So it should be called only by SkillTree.
*/
void setParent(final SkillNode parent)
{
if (parent != null) {
if (parent.getId().equals(getId()) == true || this.isParentOf(parent)) {
log.error("Oups, cyclic reference detection: skillId = " + getId() + ", parentSkillId = " + parent.getId());
throw new UserException(SkillDao.I18N_KEY_ERROR_CYCLIC_REFERENCE);
}
this.parent = parent;
this.skill.setParent(parent.getSkill());
}
}
/**
* Adds a new skill as a child of this node. It does not check whether this skill already exist as child or not! This method does not modify
* the child skill!
*/
void addChild(final SkillNode child)
{
if (child != null) {
if (child.getId().equals(getId()) == true || child.isParentOf(this)) {
log.error("Oups, cyclic reference detection: skillId = " + getId() + ", parentSkillId = " + parent.getId());
return;
}
if (this.childs == null) {
this.childs = new ArrayList<SkillNode>();
}
this.childs.add(child);
}
}
/**
* Removes a child skill of this node. This method does not modify the child skill!
*/
void removeChild(final SkillNode child)
{
if (child == null) {
log.error("Oups, child is null, can't remove it from parent.");
} else if (this.childs == null) {
log.error("Oups, this node has no childs to remove.");
} else if (this.childs.contains(child) == false) {
log.error("Oups, this node doesn't contain given child.");
} else {
log.debug("Removing child " + child.getId() + " from parent " + this.getId());
this.childs.remove(child);
}
}
@Override
public String toString()
{
final ToStringBuilder sb = new ToStringBuilder(this);
sb.append("id", getId());
Object parentId = null;
if (this.parent != null) {
parentId = this.parent.getId();
}
log.debug("id: " + this.getId() + ", parentId: " + parentId);
sb.append("parent", parentId);
sb.append("title", skill.getTitle());
sb.append("childs", this.childs);
return sb.toString();
}
public List<Integer> getAncestorIds()
{
final List<Integer> ancestors = new ArrayList<Integer>();
getAncestorIds(ancestors);
return ancestors;
}
private void getAncestorIds(final List<Integer> ancestors)
{
if (this.parent != null) {
if (ancestors.contains(this.parent.getId()) == false) {
// Paranoia setting for cyclic references.
ancestors.add(this.parent.getId());
this.parent.getAncestorIds(ancestors);
}
}
}
}