Package eu.scape_project.planning.model.tree

Source Code of eu.scape_project.planning.model.tree.TreeNode

/*******************************************************************************
* Copyright 2006 - 2012 Vienna University of Technology, 
* Department of Software Technology and Interactive Systems, IFS
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
*   http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
* This work originates from the Planets project, co-funded by the European Union under the Sixth Framework Programme.
******************************************************************************/
package eu.scape_project.planning.model.tree;

import java.io.Serializable;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;

import javax.persistence.CascadeType;
import javax.persistence.Column;
import javax.persistence.DiscriminatorColumn;
import javax.persistence.Entity;
import javax.persistence.FetchType;
import javax.persistence.GeneratedValue;
import javax.persistence.Id;
import javax.persistence.Inheritance;
import javax.persistence.JoinColumn;
import javax.persistence.Lob;
import javax.persistence.ManyToOne;
import javax.persistence.OneToMany;
import javax.validation.ConstraintViolation;
import javax.validation.Valid;

import org.hibernate.annotations.IndexColumn;

import eu.scape_project.planning.model.Alternative;
import eu.scape_project.planning.model.ChangeLog;
import eu.scape_project.planning.model.EvaluationStatus;
import eu.scape_project.planning.model.IChangesHandler;
import eu.scape_project.planning.model.ITouchable;
import eu.scape_project.planning.validation.ValidationError;

/**
* Base class for our composite hierarchy of nodes and leaves - TreeNode
* corresponds to the <code>Component</code> in the Composite Design Pattern.
*
* @author Christoph Becker
* @see Node
* @see Leaf
*/
@Entity
@Inheritance
@DiscriminatorColumn(name = "nodetype")
public abstract class TreeNode implements ITreeNode, Serializable, ITouchable, Cloneable {

    private static final long serialVersionUID = 7696297425270759326L;

    @Id
    @GeneratedValue
    protected int id;

    @ManyToOne
    @JoinColumn(name = "parent_fk", insertable = false, updatable = false)
    protected TreeNode parent;

    @Lob
    protected String name;

    @Lob
    protected String description;

    /**
     * indicates whether the weight of this node may be changed by the automatic
     * balancing of weights. If lock is true, the weight is not changed
     * automatically.
     */
    @Column(name = "locked")
    private boolean lock;

    /**
     * FIXME: why should it be more comfortable?
     *
     * determines if this node is going to have one single value for all
     * SampleRecords, or if each SampleObject has its own result value. This
     * only applies for the class Leaf, but it's comfier to include it here than
     * write complex statements in the view layer.
     */
    private boolean single;

    /**
     * This should never have a value with more than two fractional (decimal)
     * digits! Kevin suggests turning this into an integer (0 <= x <= 100(0))
     */
    protected double weight = 1.0;
   
    /**
     * the children that are contained in this node.
     */
    @Valid
    @OneToMany(fetch = FetchType.EAGER, cascade = CascadeType.ALL)
    @JoinColumn(name = "parent_fk")
    @IndexColumn(name = "indexcol", base = 1)
    protected List<TreeNode> children = new ArrayList<TreeNode>();   

    @ManyToOne(cascade = CascadeType.ALL)
    private ChangeLog changeLog = new ChangeLog();

    /**
     * emtpy default constructor
     */
    public TreeNode() {

    }

    /**
     * Instantiate a new TreeNode by its name and according weight.
     *
     * @param name
     *            of the node.
     * @param weight
     *            of the node.
     */
    public TreeNode(String name, double weight) {
        this.name = name;
        this.weight = weight;
    }

    public ChangeLog getChangeLog() {
        return changeLog;
    }

    public void setChangeLog(ChangeLog changeLog) {
        this.changeLog = changeLog;
    }

    /**
     * implement in subclasses.
     */
    public void convertChild(TreeNode n) {
    }

    // /**
    // * transient needed because of hibernate
    // */
    // @Transient
    // private List<Leaf> allLeaves = null;
    //
    // /**
    // * transient needed because of hibernate
    // */
    // @Transient
    // private int numberOfLeaves = -1;

    // /**
    // * transient needed because of hibernate
    // */
    // @Transient
    // private EvaluationStatus evaluationStatus = null;
    //
    /**
     * @return the status of evaluation of the leaves of this branch of the
     *         tree, which can be one of the values that are defined in
     *         {@link EvaluationStatus}
     * @see EvaluationStatus
     */
    public abstract EvaluationStatus getEvaluationStatus();

    /**
     * @return a List with all the leaves that are contained in this branch of
     *         the tree
     */
    public List<Leaf> getAllLeaves() {
        List<Leaf> list = new ArrayList<Leaf>();
        for (TreeNode n : children) {
            if (n instanceof Leaf) {
                Leaf leaf = (Leaf) n;
                list.add(leaf);
            } else {
                list.addAll(n.getAllLeaves());
            }
        }
        return list;
    }
   

    /**
     *
     * @return a List with all nodes that are contained in this branch of the tree
     */
    public List<TreeNode> getAllChildren() {
        List<TreeNode> list = new ArrayList<TreeNode>();
        for (TreeNode n : children) {
            list.add(n);
            if (! (n instanceof Leaf)) {
                list.addAll(n.getAllChildren());
            }
        }
        return list;
    }

    /**
     * Initializes the weights for all leaves of this TreeNode, i.e.
     * {@link #balanceNodes(List, double) balances the weights} equally. This is
     * done {@link #initWeights() recursively}.
     */
    public void initWeights() {
        if (children.isEmpty()) {
            return;
        }
        // Set weights..
        this.balanceNodes(children, 1.0);
        // ..recursively!
        for (TreeNode n : children) {
            n.initWeights();
        }
    }

    public boolean isLock() {
        return lock;
    }

    public void setLock(boolean lock) {
        this.lock = lock;
    }

    /**
     * @return the number of leaves that are contained in this branch of the
     *         tree
     */
    public int getNumberOfLeaves() {
        return getAllLeaves().size();
    }

    /**
     * This returns the absolute influence this Node has on the overall weighted
     * root value. It is worthwhile displaying this in the Analysis stage - and
     * the weighting stage - to give users a feeling how much influence each
     * node has.
     */
    public double getTotalWeight() {
        return (getParent() == null) ? getWeight() : getWeight() * getParent().getTotalWeight();
    }

    public void setName(String name) {
        this.name = name;
    }

    public String getName() {
        return name;
    }

    public String getDescription() {
        return description;
    }

    public void setDescription(String description) {
        this.description = description;
    }

    /**
     * sets the weight of this node, constraining it to [0,1]. Outliers are
     * ignored, i.e. to 0 or 1, respectively.
     *
     * @param weight
     *            the new weight to be set
     */
    public void setWeight(double weight) {
        if (weight < 0) {
            this.weight = 0.0;
        } else if (weight > 1.0) {
            this.weight = 1.0;
        } else {
            this.weight = weight;
        }
    }

    public double getWeight() {
        return weight;
    }

    public TreeNode getParent() {
        return parent;
    }

    public void setParent(TreeNode parent) {
        this.parent = parent;
    }

    public int getId() {
        return id;
    }

    public void setId(int id) {
        this.id = id;
    }

    public List<TreeNode> getChildren() {
        return children;
    }

    /**
     * sets the children, and sets self as parent of each of the nodes in the
     * provided list
     *
     * @param children
     * @see TreeNode#setParent(TreeNode)
     */
    public void setChildren(List<TreeNode> children) {
        this.children = children;
        for (TreeNode n : children) {
            n.setParent(this);
        }
    }

    /**
     * @return if this is a {@link Leaf}, false otherwise.
     */
    public abstract boolean isLeaf();

    /**
     * returns the sibling that comes after self, or <code>null</code>, if
     * either I am the last one or I don't have a parent.
     *
     * @return the next sibling, or null
     */
    public TreeNode getNextSibling() {
        if (parent != null) {
            return parent.getNextChild(this);
        } else {
            return null;
        }
    }

    /**
     * returns the TreeNode that comes next in the children's list after the one
     * that is being passed as parameter
     *
     * @param n
     *            node for which the next sibling shall be returned
     * @return the next sibling of the provided node, or null, if there is none.
     * @throws {@link IllegalArgumentException} if a foreign child is provided
     *         as a parameter
     */
    public TreeNode getNextChild(TreeNode n) {
        int index = children.indexOf(n);
        if (index == -1) {
            throw new IllegalArgumentException("This node is not my child. And I don't even think of adopting it.");
        }
        if (index == children.size()) {
            return null;
        } else {
            return children.get(index + 1);
        }
    }

    /**
     * inits the values of all children
     *
     * @param list
     *            the alternatives for which values shall be created
     * @param records
     *            the number of sample objects
     * @param initLinkage
     *            indicates whether the linkage between scales and values shall
     *            be created.
     * @see Leaf#initValues(List, int, boolean)
     */
    public void initValues(List<Alternative> list, int records, boolean initLinkage) {
        for (TreeNode n : children) {
            n.initValues(list, records, initLinkage);
        }
    }

    public boolean isValueMapProperlyInitialized(List<Alternative> alternatives, int numberRecords) {
        for (TreeNode n : children) {
            if (n.isValueMapProperlyInitialized(alternatives, numberRecords) == false) {
                return false;
            }
        }

        return true;
    }

    public boolean isSingle() {
        return single;
    }

    public void setSingle(boolean single) {
        this.single = single;
    }

    /**
     * balances the importance weightings of self and my siblings by calling
     * self's parent.
     *
     * @see #balanceWeights(TreeNode)
     */
    public void balanceWeights() {
        if (parent != null) {
            parent.balanceWeights(this);
        }
    }

    /**
     * balances the weights of the children according to the newly changed
     * weight of one child, and sets the changed child to locked state. If the
     * sum of locked weights plus the changed one is over 1.0, the weight of the
     * freshly changed node is cut down to the remaining difference of locked
     * weights to 1.0. The other unlocked weights are balanced evenly.
     *
     * @param changed
     *            the node where the weight was just changed.
     */
    public void balanceWeights(TreeNode changed) {
        // We need to things:
        // 1. The list of nodes where the weights are not locked
        List<TreeNode> unlocked = new ArrayList<TreeNode>();
        // 2. the sum of weights of the nodes where lock is true
        double lockedWeight = 0.0;

        // We calculate these two things:
        for (TreeNode n : children) {
            if (n != changed) {
                if (n.isLock()) {
                    lockedWeight += n.getWeight();
                } else {
                    unlocked.add(n);
                }
            }
        }

        if (lockedWeight + changed.getWeight() >= 1.0) {
            /*
             * Either the new change results in sum >= 1 in which case the
             * changed gets only as much as weighting as is left in the pot, or
             * there is enough left.
             *
             * First case treated here. If the locked weights are already too
             * high, we dont give it everything: Even the unlocked nodes need a
             * minimum of 0.01, so that amount is kept aside.
             */
            changed.setWeight(1.0 - lockedWeight - 0.01 * unlocked.size());
            balanceNodes(unlocked, 0.01 * unlocked.size());
        } else {
            /*
             * Or the sum is < 1.0, which means we have to spread the remaining
             * (1.0 - sum) evenly across the nodes that are not locked. Because
             * the weights we set have to be fully transparent to the user
             * (because he has to make sure that the sum is 1.0 in the end) we
             * must not set a weight to something with more than two fractional
             * (decimal!) digits!
             */
            double weightToGive = 1.0 - (lockedWeight + changed.getWeight());
            balanceNodes(unlocked, weightToGive);
        }
        // this node has been changed
        changed.touch();
        changed.setLock(true);
    }

    /**
     * Spreads the given weight equally among the given TreeNodes, never using
     * more than 2 fractional (decimal) digits
     *
     * @author Kevin Stadler
     */
    private void balanceNodes(List<TreeNode> elements, double weightToGive) {
        int i = elements.size();
        /*
         * "i" is the number of unlocked children of this node which have not
         * been assigned a new weight yet
         */
        for (TreeNode n : elements) {
            // Round it to two decimal digits
            double weight = Math.round((100.0 * weightToGive) / i) / 100.0;
            n.setWeight(weight);
            weightToGive -= weight;
            i--;
            // this node has been changed
            n.touch();
        }
    }

    /**
     * checks if this is a child. If it has a parent, it must be a child, so...
     *
     * @return <code>not parent is null :)</code>
     */
    public boolean isChild() {
        return parent != null;
    }

    /**
     * checks whether this node and its children are specified completely. This
     * is overridden in Node and Leaf!
     *
     * @param errors
     *            TODO
     * @see Node#isCompletelySpecified(List<ValidationError>)
     * @see Leaf#isCompletelySpecified(List<ValidationError>)
     * @return false
     */
    public boolean isCompletelySpecified(List<ValidationError> errors) {
        return false;
    }

    /**
     * checks whether this node and its children are evaluated completely. This
     * is overridden in Node and Leaf!
     *
     * @param errors
     *            TODO
     * @see Node#isCompletelyEvaluated(List, List)
     * @see Leaf#isCompletelyEvaluated(List, List)
     * @return false
     */
    public boolean isCompletelyEvaluated(List<Alternative> alternatives, List<ValidationError> errors) {
        return false;
    }

    /**
     * checks whether this node and its children have complete transformation
     * settings. This is overridden in Node and Leaf!
     *
     * @param errors
     *            TODO
     * @see Node#isCompletelyTransformed(List)
     * @see Leaf#isCompletelyTransformed(List)
     * @return false
     */
    public boolean isCompletelyTransformed(List<ValidationError> errors) {
        return false;
    }

    /**
     * Checks whether this node is correctly weighted. This is overridden in
     * Node and Leaf!
     *
     * @param errors
     *            TODO
     * @see Node#isCorrectlyWeighted(List)
     * @see Leaf#isCorrectlyWeighted(List)
     * @return false
     */
    public boolean isCorrectlyWeighted(List<ValidationError> errors) {
        return false;
    }

    /**
     * Touches every thing in the hierarchy: this treenode, all nodes and
     * children down to the leaves, scales, transformers
     *
     * @see Leaf#touchAll()
     */
    public void touchAll() {
        touch();
        for (TreeNode n : children) {
            n.touchAll();
        }
    }

    /**
     * @see #touchAll()
     */
    public void touchAll(String username) {
        touch(username);
        for (TreeNode n : children) {
            n.touchAll(username);
        }
    }

    public void touch() {
        changeLog.touch();
    }

    public void touch(String username) {
        changeLog.touch(username);
    }

    public boolean isChanged() {
        return changeLog.isAltered();
    }

    public boolean isDirty() {
        return changeLog.isDirty();
    }

    /**
     * @see ITouchable#handleChanges(IChangesHandler)
     */
    public void handleChanges(IChangesHandler h) {
        // let the changeshandler handle this instance
        h.visit(this);
        // and call handleChanges of all child elements
        for (TreeNode node : children) {
            node.handleChanges(h);
        }
    }

    /**
     * returns a clone of self. Implemented for storing and inserting fragments.
     * Subclasses obtain a shallow copy by invoking this method, then modifying
     * the fields required to obtain a deep copy of this object.
     */
    public TreeNode clone() {
        try {
            TreeNode clone = (TreeNode) super.clone();

            clone.id = 0;

            clone.setParent(null);

            // created-timestamp is automatically set to now
            clone.setChangeLog(new ChangeLog(this.getChangeLog().getChangedBy()));

            if (this.getChildren() != null) {
                List<TreeNode> clonedChildren = new ArrayList<TreeNode>();
                for (TreeNode child : this.getChildren()) {
                    clonedChildren.add(child.clone());
                }
                clone.setChildren(clonedChildren);
            }

            return clone;
        } catch (CloneNotSupportedException e) {
            // Never thrown
            return null;
        }
    }

    public void convertToNode(Leaf l) {
        Node node = new Node();
        node.setName(l.getName());
        node.setWeight(l.getWeight());

        children.set(children.indexOf(l), node);
        node.setParent(this);
    }

    public void convertToLeaf(Node n) {
        Leaf leaf = new Leaf();
        leaf.setName(n.getName());
        leaf.setWeight(n.getWeight());

        children.set(children.indexOf(n), leaf);
        leaf.setParent(this);
    }

    public Set<TreeNode> getAllParents() {
        Set<TreeNode> parents = null;
        if (parent == null) {
            parents = new HashSet<TreeNode>();
        } else {
            parents = parent.getAllParents();
            parents.add(parent);
        }
        return parents;
    }

    /**
     * Normalize the weights of this node.
     *
     * @see #normalizeWeights()
     *
     * @param recoursive
     *            If true, this function will be applied to this node and all
     *            its children recoursivly. Otherwise only this node will be
     *            normalized.
     */
    public abstract void normalizeWeights(boolean recursive);

    /**
     * Make sure the weights are normalized. This means that all values are
     * between 0 and 1 and the sum of all children of a tree node is equal 1.
     *
     * This function normalizes the weights of this node and is then applied to
     * all children recoursivly.
     */
    public void normalizeWeights() {
        normalizeWeights(true);
    }

    public void walkTree(ITreeWalker treeWalker) {
        treeWalker.walk(this);
        for (TreeNode node : children) {
            node.walkTree(treeWalker);
        }
    }
}
TOP

Related Classes of eu.scape_project.planning.model.tree.TreeNode

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.