/******************************************************************************* * 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.List; import java.util.Set; import javax.persistence.CascadeType; import javax.persistence.Entity; import javax.persistence.FetchType; import javax.persistence.GeneratedValue; import javax.persistence.Id; import javax.persistence.OneToOne; import javax.persistence.Transient; import javax.validation.Valid; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import eu.scape_project.planning.model.Alternative; import eu.scape_project.planning.model.Values; import eu.scape_project.planning.model.values.Value; import eu.scape_project.planning.validation.ValidationError; /** * This class is the container of the ObjectiveTree * * @author Christoph Becker * @author Florian Motlik * @author Kevin Stadler * @see TreeNode * @see Node * @see Leaf */ @Entity public class ObjectiveTree implements Serializable { private static Logger log = LoggerFactory.getLogger(ObjectiveTree.class); private static final long serialVersionUID = -4894131307261590502L; /** * indicates where the initial balancing of weights in the objective tree * has been performed yet. * * @see #initWeights() */ private boolean weightsInitialized = false; /** * Checks if weights have been initialised ({@link #weightsInitialized}), * and if not, performs initialisation and sets {@link #weightsInitialized} * to true. The initialisation sets the weights of all leaves in the tree, * i.e. distributes all weights equally. * * @see TreeNode#initWeights() * @see #weightsInitialized */ public void initWeights() { if (this.isWeightsInitialized()) { log.debug("Weights already initialized"); } else { log.debug("Initializing weights for the first time"); root.setWeight(1.0); root.initWeights(); this.setWeightsInitialized(true); } } /** * inits the value objects throughout the tree. * * @see #initValues(List, int, boolean) * @param list * of Alternatives * @param records * number of samples */ public void initValues(List<Alternative> list, int records) { initValues(list, records, false); } /** * inits the value objects throughout the tree. * * @see Leaf#initValues(List, int, boolean) * @param list * @param records * @param initLinkage */ public void initValues(List<Alternative> list, int records, boolean initLinkage) { root.initValues(list, records, initLinkage); } /** * reference to the root node of the objective tree. */ @Valid @OneToOne(cascade = CascadeType.ALL, fetch = FetchType.EAGER) private TreeNode root; @Id @GeneratedValue private int id; @Transient private boolean mappingExistent; public int getId() { return id; } public void setId(int id) { this.id = id; } /** * empty default constructor * */ public ObjectiveTree() { } /** * Checks the whole tree whether evaluation values for the given records and * alternative exist. This function is used then asking the user before * removing a record or alternative and the values associated with it from * the objective tree. * * @param records * indices of sample records in the records list that should be * checked * @param a * the Alternative, or null if all alternatives should be checked * @return true if any leaf of the tree contains a value for the given * records and alternative */ public boolean hasValues(int records[], Set<String> checkAlternatives) { List<Leaf> list = root.getAllLeaves(); if (list.size() > 0 && records.length > 0) { for (Leaf l : list) { for (String alt : checkAlternatives) { if (hasValuesForRecords(l.getValues(alt), records)) { return true; } } } } return false; } /** * Checks whether the given Values-Object contains values for the specified * records * * @param values * the Values-Object to be checked * @param records * an array of record-indices * @return true if the Values-Object contains values at any of the indices */ private boolean hasValuesForRecords(Values values, int records[]) { if (values != null) { List<Value> list = values.getList(); for (int record : records) { if (record < list.size() && list.get(record) != null) { return true; } } } return false; } public void setRoot(TreeNode root) { this.root = root; } public TreeNode getRoot() { return root; } /** * prints debug information for the whole tree */ public void debug() { debug(root); } /** * recursively prints debug information on the provided node and its * children */ private void debug(TreeNode node) { if (node instanceof Leaf) { Leaf l = (Leaf) node; log.debug("leaf: " + node.getName() + " has " + l.getValueMap().size() + " values."); for (String a : l.getValueMap().keySet()) { log.debug("---" + a); } } for (TreeNode n : node.getChildren()) { debug(n); } } /** * removes associated evaluation {@link Values} for a given list of * alternatives and a give record index from all leaves in this tree. * * @param list * list of Alternatives for which values shall be removed * @param record * index of the record for which values shall be removed * @see Leaf#removeValues(List, int) */ public void removeValues(List<Alternative> list, int record) { for (Leaf l : root.getAllLeaves()) { l.removeValues(list, record); } } /** * Removes all evaluation-values associated with the given alternative from * all leaves of the tree */ public void removeValues(Alternative a) { for (Leaf l : root.getAllLeaves()) { l.getValueMap().remove(a.getName()); } } /** * checks if this tree is specifed completely (i.e. all nodes have some * leaves as children and all leaves have a scale) * * @param errors * the list to which validation errors are appended * @see TreeNode#isCompletelySpecified(List<ValidationError>) * @see Leaf#isCompletelySpecified(List<ValidationError>) */ public boolean isCompletelySpecified(List<ValidationError> errors) { return root.isCompletelySpecified(errors); } /** * checks if this tree is evaluated completely (i.e. all leaves have * evaluation values for all alternatives and records) * * @param inputList * the list to which error messages are appended * @see TreeNode#isCompletelyEvaluated(List, List) * @see Leaf#isCompletelyEvaluated(List, List) */ boolean isCompletelyEvaluated(List<Alternative> inputList) { return false; } public boolean isWeightsInitialized() { return weightsInitialized; } public void setWeightsInitialized(boolean weightsInitialized) { this.weightsInitialized = weightsInitialized; } public boolean isMappingExistent() { for (Leaf leaf : getRoot().getAllLeaves()) { if (leaf.isMapped()) return true; } return false; } /** * this method iterates through all leaves and updates the value maps, * changing the name of the alternative to the new one. * * @param oldName * old name to be updated * @param newName * new name to be used instead of oldName * @see Leaf#updateAlternativeName(String, String) */ public void updateAlternativeName(String oldName, String newName) { for (Leaf leaf : root.getAllLeaves()) { leaf.updateAlternativeName(oldName, newName); } } /** * <ul> * <li> * removes all {@link Values} from all leaves which are not mapped by one of * the names provided in the list</li> * <li> * removes all {@link Value} objects in the {@link Values} which are out of * the index of the sample records (which should not happen, but apparently * we have some projects where this is the case), or where a leaf is single * and there is more than one {@link Value}</li> * </ul> * * @param alternatives * list of names of alternatives * @param records * number of records in the project * @return number of {@link Values} and {@link Value} objects removed (sum * of instances) * @see Leaf#removeLooseValues(List) */ public int removeLooseValues(List<String> alternatives, int records) { int number = 0; for (Leaf l : getRoot().getAllLeaves()) { number += l.removeLooseValues(alternatives, records); } return number; } /** * This makes sure that the scales of all leaves match the scales implied by * the criterion URI Can e.g. be used after freemind import, to complete the * information about the criterion */ public void adjustScalesToMeasurements() { for (Leaf l : getRoot().getAllLeaves()) { if (l.getMeasure() != null) { l.adjustScale(l.getMeasure().getScale()); } } } }