/******************************************************************************* * 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.util.Collection; import java.util.HashSet; import java.util.List; import javax.persistence.DiscriminatorValue; import javax.persistence.Entity; import org.slf4j.LoggerFactory; import eu.scape_project.planning.model.Alternative; import eu.scape_project.planning.model.EvaluationStatus; import eu.scape_project.planning.validation.ValidationError; /** * A node in the objective tree contains children and does not contain any * measurement unit and values. Part of our implementation of the Composite * Design Pattern, cf. TreeNode and Leaf - Node corresponds to the * <code>Composite</code> * This will be named REQUIREMENT with the next major release. * CRITERIA are then the measurable requirements, i.e. the now-called Leaves. * @see Leaf * @see TreeNode * @author Christoph Becker */ @Entity @DiscriminatorValue("N") public class Node extends TreeNode { private static final long serialVersionUID = -4323424291922910124L; /** * empty default constructor */ public Node() { } /** * adds the provided node as a child to this. * @param n the node to be added as a child */ public void addChild(TreeNode n) { children.add(n); n.setParent(this); } /** * removes the provided node from the list of children. * This does <b>not</b> check if the node actually <em>is</em> a child, * so if it is not, it loses its parent reference. * @param n */ public void removeChild(TreeNode n) { children.remove(n); n.setParent(null); } /** * @param n */ void convertChild(Node n) { Leaf l = new Leaf(); l.setName(n.getName()); children.set(children.indexOf(n), l); n.setParent(this); } /** * @param l */ void convertChild(Leaf l) { Node n = new Node(); n.setName(l.getName()); children.set(children.indexOf(l), n); n.setParent(this); } /** * converts this Node to a Leaf */ public void convert() { TreeNode n = getParent(); if (n != null) { n.convertChild(this); } else { LoggerFactory.getLogger(this.getClass()).warn( "convert called on root node - ignoring"); } } @Override /** * @return false */ public boolean isLeaf() { return false; } /** * unused atm, to be used in Plato 2.2+ */ public EvaluationStatus getEvaluationStatus() { int missing = 0; int complete = 0; for (TreeNode n : children) { if (n.getEvaluationStatus() == EvaluationStatus.NONE) { missing++; } else if (n.getEvaluationStatus() == EvaluationStatus.COMPLETE) { complete++; } } if (missing == children.size()) { return EvaluationStatus.NONE; } else if (complete == children.size()) { return EvaluationStatus.COMPLETE; } else { return EvaluationStatus.PARTLY; } } /** * Checks if there children of this node with duplicate names * @param errors A list of error messages * @return true if two children have the same name */ private boolean hasDuplicates(List<ValidationError> errors) { boolean duplicates = false; Collection<String> childNames = new HashSet<String>(); for (TreeNode node : this.children) { if (childNames.contains(node.getName())) { errors.add(new ValidationError("Node '" + this.getName() + "' has several children with the name '" + node.getName() + "'", this)); duplicates = true; } else { childNames.add(node.getName()); } } return duplicates; } /** * Checks if this node is completely specified. * I.e., if there are no children, something is wrong - * and if there are children with duplicate names, something is wrong too. * If not, we return true. * @param errors list to which to append validation errors * @see TreeNode#isCompletelySpecified(List<ValidationError>) * @see Leaf#isCompletelySpecified(List<ValidationError>) */ @Override public boolean isCompletelySpecified(List<ValidationError> errors) { boolean noError = true; if (this.children.size() == 0) { errors.add(new ValidationError("Node " + this.getName() + " has no Children.", this)); noError = false; } if (this.hasDuplicates(errors)) { noError = false; } return noError; } @Override /** * unused - we go directly down to allLeaves(). * TODO This should actually check and aggregate the evaluation status of the children * @return true (always!) */ public boolean isCompletelyEvaluated(List<Alternative> alternatives, List<ValidationError> errors) { return true; } /** * unused - we go directly down to allLeaves(). * TODO This should actually check and aggregate the transformation * status of the children * @return true (always!) */ @Override public boolean isCompletelyTransformed(List<ValidationError> errors) { return true; } /** * checks the weighting of the children * @return true if the sum of weights of the children is equal to 100 percent. */ @Override public boolean isCorrectlyWeighted(List<ValidationError> errors) { /* * Because IEEE Floating-Point-Arithmetic is inherently funny (did you * know that 0.09*10 != 0.9?) we have to add up the weights*100 rounded * to longs and check whether their sum is 100! */ Integer sum = 0; for (TreeNode child : this.children) { sum += (int)Math.round(100.0 * child.getWeight()); } if (sum.equals(100)) { return true; } errors.add(new ValidationError("The sum of the weights of node " + this.getName() + "'s children is not 1 (but " + sum / 100.0 + ").", this)); return false; } /** * Returns a clone of self. Does not clone more than super * @see TreeNode#clone() */ @Override public TreeNode clone() { Node clone = (Node) super.clone(); return clone; } public void normalizeWeights(boolean recursive) { // first determine the sum of weights of all direct children double weightSum = 0; for (TreeNode child : children) { weightSum += child.getWeight(); } // if the sum is 0 then spread the weight equally (= 1 / number of // children) if (weightSum == 0) { for (TreeNode child : children) { child.setWeight(1 / children.size()); } } else { // otherwise normalize the weights - divide all weights // with the total sum -> this way the new sum will be = 1; // TODO: handle crazy floating point arithmetics in java // (precision is futile, you will be approximated!). for (TreeNode child : children) { child.setWeight(child.getWeight()/ weightSum); } } // apply this function recursively to all children (if selected) if (recursive) { for (TreeNode child : children) { child.normalizeWeights(); } } } }