/*
* This file is part of ADDIS (Aggregate Data Drug Information System).
* ADDIS is distributed from http://drugis.org/.
* Copyright © 2009 Gert van Valkenhoef, Tommi Tervonen.
* Copyright © 2010 Gert van Valkenhoef, Tommi Tervonen, Tijs Zwinkels,
* Maarten Jacobs, Hanno Koeslag, Florin Schimbinschi, Ahmad Kamal, Daniel
* Reid.
* Copyright © 2011 Gert van Valkenhoef, Ahmad Kamal, Daniel Reid, Florin
* Schimbinschi.
* Copyright © 2012 Gert van Valkenhoef, Daniel Reid, Joël Kuiper, Wouter
* Reckman.
* Copyright © 2013 Gert van Valkenhoef, Joël Kuiper.
*
* This program 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, either version 3 of the License, or
* (at your option) any later version.
*
* This program 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.drugis.addis.entities.treatment;
import java.util.Arrays;
import java.util.HashSet;
import java.util.Set;
import org.drugis.addis.entities.AbstractDose;
import org.drugis.addis.entities.AbstractEntity;
import org.drugis.addis.entities.DoseUnit;
import org.drugis.addis.entities.Drug;
import org.drugis.addis.entities.Entity;
import org.drugis.addis.entities.FixedDose;
import org.drugis.addis.entities.FlexibleDose;
import org.drugis.addis.entities.TypeWithName;
import org.drugis.addis.entities.UnknownDose;
import org.drugis.common.EqualsUtil;
import com.jgoodies.binding.list.ArrayListModel;
import com.jgoodies.binding.list.ObservableList;
import edu.uci.ics.jung.graph.util.Pair;
public class TreatmentCategorization extends AbstractEntity implements Comparable<TreatmentCategorization>, TypeWithName {
public static final ChoiceNode ROOT_NODE = new ChoiceNode(AbstractDose.class, "class");
public static final String PROPERTY_DOSE_UNIT = "doseUnit";
public static final String PROPERTY_DRUG = "drug";
public static final String PROPERTY_CATEGORIES = "categories";
private final ObservableList<Category> d_categories = new ArrayListModel<Category>();
private Drug d_drug;
private DecisionTree d_decisionTree;
private final DoseUnit d_doseUnit;
private String d_name;
/**
* Create a TreatmentCategorization with a decision tree consisting solely of {@link TreatmentCategorization#ROOT_NODE}.
* @param name Name for the categorization.
* @param drug Drug to categorize.
* @param unit Unit to perform dose comparisons in.
* @return A new TreatmentCategorization.
*/
public static TreatmentCategorization createBare(String name, Drug drug, DoseUnit unit) {
return new TreatmentCategorization(name, drug, unit, false);
}
/**
* Create a TreatmentCategorization with a default decision tree, having branches for UnknownDose, FixedDose and FlexibleDose.
* @param name Name for the categorization.
* @param drug Drug to categorize.
* @param unit Unit to perform dose comparisons in.
* @return A new TreatmentCategorization.
*/
public static TreatmentCategorization createDefault(String name, Drug drug, DoseUnit unit) {
return new TreatmentCategorization(name, drug, unit, true);
}
/**
* Create a TreatmentCategorization with a default decision tree, having branches for UnknownDose, FixedDose and FlexibleDose.
* @return A new TreatmentCategorization.
*/
public static TreatmentCategorization createDefault() {
return createDefault("", null, DoseUnit.createMilliGramsPerDay());
}
/**
* Create a trivial TreatmentCategorization that will accept any dose of the given drug.
* @param drug Drug to accept.
* @return A new TreatmentCategorization.
*/
public static TreatmentCategorization createTrivial(Drug drug) {
TreatmentCategorization categorization = new TreatmentCategorization("", drug, DoseUnit.createMilliGramsPerDay(), false);
Category category = new Category(categorization);
categorization.addCategory(category);
categorization.d_decisionTree = new DecisionTree(new LeafNode(category));
return categorization;
}
private TreatmentCategorization(final String name, final Drug drug, final DoseUnit unit, boolean withDefault) {
d_name = name;
d_drug = drug;
d_doseUnit = unit;
d_decisionTree = new DecisionTree(ROOT_NODE);
if (withDefault) {
addDefaultEdges(d_decisionTree);
}
}
private static void addDefaultEdges(final DecisionTree tree) {
final DecisionTreeNode root = tree.getRoot();
tree.addEdge(new TypeEdge(UnknownDose.class), root, new LeafNode());
tree.addEdge(new TypeEdge(FixedDose.class), root, new LeafNode());
tree.addEdge(new TypeEdge(FlexibleDose.class), root, new LeafNode());
}
public boolean isTrivial() {
return getRootNode() instanceof LeafNode;
}
@Override
public String getName() {
return d_name;
}
public void setName(final String name) {
final String oldVal = d_name;
d_name = name;
firePropertyChange(PROPERTY_NAME, oldVal, d_name);
}
public Drug getDrug() {
return d_drug;
}
public void setDrug(final Drug drug) {
final Drug oldVal = d_drug;
d_drug = drug;
firePropertyChange(PROPERTY_DRUG, oldVal, drug);
}
public DoseUnit getDoseUnit() {
return d_doseUnit;
}
public void addCategory(final Category categoryNode) {
d_categories.add(categoryNode);
}
public ObservableList<Category> getCategories() {
return d_categories;
}
public Category getCategory(final AbstractDose dose) {
return d_decisionTree.decide(dose).getCategory();
}
@Override
public String getLabel() {
return (getDrug() == null ? "" : getDrug().getLabel()) + " " + getName();
}
@Override
public String toString() {
return getLabel();
}
@Override
public Set<? extends Entity> getDependencies() {
return new HashSet<Entity>(Arrays.asList(d_drug, d_doseUnit.getUnit()));
}
public DecisionTree getDecisionTree() {
return d_decisionTree;
}
public DecisionTreeNode getRootNode() {
return d_decisionTree.getRoot();
}
public Pair<RangeEdge> splitRange(final ChoiceNode parent, final double value, final boolean lowerRangeOpen) {
final RangeEdge edge = (RangeEdge) d_decisionTree.findMatchingEdge(parent, value);
return splitRange(edge, value, lowerRangeOpen);
}
/**
* Add a cut-off value. This splits the existing range in two.
* The lower range will always be initialized with the child node of the original range, the higher range will be excluded by default.
* @param range The range to split.
* @param value The cut-off value.
* @param lowerRangeOpen If true, the upper bound of the lower range will be open.
* Otherwise, it will be closed. Vice versa for the lower bound of the upper range.
*/
public Pair<RangeEdge> splitRange(final RangeEdge edge, final double value, final boolean lowerRangeOpen) {
final DecisionTreeNode parent = d_decisionTree.getEdgeSource(edge);
final DecisionTreeNode child = d_decisionTree.getEdgeTarget(edge);
final Pair<RangeEdge> ranges = splitOnValue(edge, value, lowerRangeOpen);
d_decisionTree.removeChild(child);
d_decisionTree.addChild(ranges.getFirst(), parent, child);
d_decisionTree.addChild(ranges.getSecond(), parent, new LeafNode());
return ranges;
}
private static Pair<RangeEdge> splitOnValue(final RangeEdge range, final double value, final boolean isLowerRangeOpen) {
final RangeEdge left = new RangeEdge(range.getLowerBound(), range.isLowerBoundOpen(), value, isLowerRangeOpen);
final RangeEdge right = new RangeEdge(value, !isLowerRangeOpen, range.getUpperBound(), range.isUpperBoundOpen());
return new Pair<RangeEdge>(left, right);
}
@Override
public int compareTo(TreatmentCategorization o) {
int drugCompare = d_drug.compareTo(o.d_drug);
if (drugCompare != 0) {
return drugCompare;
}
return d_name.compareTo(o.d_name);
}
@Override
public boolean equals(Object obj) {
if (obj instanceof TreatmentCategorization) {
TreatmentCategorization other = (TreatmentCategorization) obj;
return EqualsUtil.equal(d_drug, other.d_drug) && EqualsUtil.equal(d_name, other.d_name);
}
return false;
}
/**
* The implementation of deepEquals(Entity) for TreatmentCategorization and
* Category is complicated by their circular dependency. However, Category
* is just a (TreatmentCategorization, String) pair, and here we can assume
* the TreatmentCategorizations to be known. Therefore it suffices to
* shallow equals the categories.
*/
@Override
public boolean deepEquals(Entity obj) {
if (equals(obj)) {
TreatmentCategorization other = (TreatmentCategorization) obj;
return d_categories.equals(other.d_categories)
&& d_drug.deepEquals(other.d_drug)
&& d_decisionTree.equivalent(other.d_decisionTree);
}
return false;
}
@Override
public int hashCode() {
return EqualsUtil.hashCode(d_drug) * 31 + EqualsUtil.hashCode(d_name);
}
}