/*
* 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.presentation.wizard;
import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.SortedSet;
import java.util.TreeSet;
import org.apache.commons.collections15.CollectionUtils;
import org.apache.commons.collections15.Predicate;
import org.apache.commons.lang.ArrayUtils;
import org.drugis.addis.entities.Arm;
import org.drugis.addis.entities.Domain;
import org.drugis.addis.entities.Drug;
import org.drugis.addis.entities.Indication;
import org.drugis.addis.entities.OutcomeMeasure;
import org.drugis.addis.entities.Study;
import org.drugis.addis.entities.Variable;
import org.drugis.addis.entities.analysis.MetaAnalysis;
import org.drugis.addis.entities.analysis.NetworkMetaAnalysis;
import org.drugis.addis.entities.treatment.Category;
import org.drugis.addis.entities.treatment.TreatmentCategorization;
import org.drugis.addis.entities.treatment.TreatmentDefinition;
import org.drugis.addis.presentation.ModifiableHolder;
import org.drugis.addis.presentation.PresentationModelFactory;
import org.drugis.addis.presentation.SelectableStudyCharTableModel;
import org.drugis.addis.presentation.SelectableTreatmentDefinitionsGraphModel;
import org.drugis.addis.presentation.StudyListPresentation;
import org.drugis.addis.presentation.TreatmentDefinitionsGraphModel;
import org.drugis.addis.presentation.TreatmentDefinitionsGraphModel.Edge;
import org.drugis.addis.presentation.TreatmentDefinitionsGraphModel.Vertex;
import org.drugis.addis.presentation.ValueHolder;
import org.drugis.common.CollectionUtil;
import org.drugis.common.beans.AffixedObservableList;
import org.drugis.common.beans.FilteredObservableList;
import org.drugis.common.event.IndifferentListDataListener;
import org.jgrapht.alg.ConnectivityInspector;
import org.jgrapht.event.GraphEdgeChangeEvent;
import org.jgrapht.event.GraphListener;
import org.jgrapht.event.GraphVertexChangeEvent;
import com.jgoodies.binding.list.ArrayListModel;
import com.jgoodies.binding.list.ObservableList;
import com.jgoodies.binding.value.AbstractValueModel;
import com.jgoodies.binding.value.ValueModel;
public class NetworkMetaAnalysisWizardPM extends AbstractAnalysisWizardPresentation<MetaAnalysis> {
private static final String TEMPLATE_DEFINITIONS_DESCRIPTION =
"Select the %1$s to be used for the network meta-analysis. Click to select (green) or deselect (gray). " +
"To continue, (1) at least %2$s must be selected, and (2) all selected %1$s must be connected.";
private final TreatmentDefinitionsGraphModel d_overviewGraph;
private final ValueHolder<Boolean> d_selectedStudyGraphConnectedModel;
protected PresentationModelFactory d_pmf;
protected ModifiableHolder<OutcomeMeasure> d_outcomeHolder;
private ObservableList<OutcomeMeasure> d_outcomes = new ArrayListModel<OutcomeMeasure>();
/** First graph containing only Trivial Categorizations (previously DrugSets) **/
protected final ObservableList<TreatmentDefinition> d_rawTreatmentDefinitions;
protected final SelectableTreatmentDefinitionsGraphModel d_rawAlternativesGraph;
/** Second graph containing definitions transformed by the selection of TreatmentCategorizations **/
protected final ObservableList<TreatmentDefinition> d_refinedTreatmentDefinitions;
protected final SelectableTreatmentDefinitionsGraphModel d_refinedAlternativesGraph;
private final StudiesMeasuringValueModel d_studiesMeasuringValueModel;
protected final Map<Study, Map<TreatmentDefinition, ModifiableHolder<Arm>>> d_selectedArms;
protected final SelectableStudyCharTableModel d_selectableStudyListPm;
private final ObservableList<Study> d_studiesEndpointIndication;
private final Map<Drug, ValueHolder<TreatmentCategorization>> d_selectedCategorizations = new HashMap<Drug, ValueHolder<TreatmentCategorization>>();
private final ObservableList<Study> d_selectableStudies = new ArrayListModel<Study>();
private boolean d_rebuildRawNeeded = true;
private boolean d_armSelectionRebuildNeeded = true;
/**
* Create a filtered list of studies that includes only those studies that
* compare at least two of the definitions on the given variable.
*/
public static List<Study> filterStudiesComparing(final Variable var, final Collection<Study> studies, final Collection<TreatmentDefinition> defs) {
return (List<Study>) CollectionUtils.select(studies, new Predicate<Study>() {
public boolean evaluate(Study s) {
int count = 0;
for (TreatmentDefinition def : defs) {
if (!s.getMeasuredArms(var, def).isEmpty()) {
++count;
}
}
return count > 1;
}
});
}
/**
* Create a filtered list of definitions that includes only those definitions that
* are measured on the given variable in at least one study.
*/
public static List<TreatmentDefinition> filterDefinitionsMeasured(final Variable var, Collection<TreatmentDefinition> defs, final Collection<Study> studies) {
return (List<TreatmentDefinition>) CollectionUtils.select(defs, new Predicate<TreatmentDefinition>() {
public boolean evaluate(TreatmentDefinition def) {
for (Study s : studies) {
if (!s.getMeasuredArms(var, def).isEmpty()) {
return true;
}
}
return false;
}
});
}
public NetworkMetaAnalysisWizardPM(Domain d, PresentationModelFactory pmf) {
super(d, d.getMetaAnalyses());
d_pmf = pmf;
d_outcomeHolder = new ModifiableHolder<OutcomeMeasure>();
d_indicationHolder.addPropertyChangeListener(new ClearValueModelsOnPropertyChangeListener(d_outcomeHolder));
updateOutcomes();
d_indicationHolder.addValueChangeListener(new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
updateOutcomes();
}
});
d_studiesEndpointIndication = createStudiesIndicationOutcome();
d_studiesEndpointIndication.addListDataListener(new IndifferentListDataListener() {
protected void update() {
d_rebuildRawNeeded = true;
}
});
d_rawTreatmentDefinitions = new ArrayListModel<TreatmentDefinition>();
d_rawAlternativesGraph = buildRawAlternativesGraph();
d_refinedTreatmentDefinitions = new ArrayListModel<TreatmentDefinition>();
d_refinedAlternativesGraph = buildRefinedAlternativesGraph();
getSelectedRefinedTreatmentDefinitions().addListDataListener(new IndifferentListDataListener() {
protected void update() {
d_armSelectionRebuildNeeded = true;
}
});
d_studiesMeasuringValueModel = new StudiesMeasuringValueModel();
d_selectedArms = new HashMap<Study, Map<TreatmentDefinition, ModifiableHolder<Arm>>>();
d_selectableStudyListPm = createSelectableStudyListPm();
d_overviewGraph = new TreatmentDefinitionsGraphModel(getSelectedStudies(), getSelectedRefinedTreatmentDefinitions(), getOutcomeMeasureModel());
d_selectedStudyGraphConnectedModel = new StudySelectionCompleteListener();
}
public ObservableList<TreatmentDefinition> getSelectedRawTreatmentDefinitions() {
return d_rawAlternativesGraph.getSelectedDefinitions();
}
public ObservableList<TreatmentDefinition> getSelectedRefinedTreatmentDefinitions() {
return d_refinedAlternativesGraph.getSelectedDefinitions();
}
public TreatmentDefinitionsGraphModel getOverviewGraph(){
return d_overviewGraph;
}
protected SelectableTreatmentDefinitionsGraphModel buildRawAlternativesGraph() {
return new SelectableTreatmentDefinitionsGraphModel(getStudiesEndpointAndIndication(), d_rawTreatmentDefinitions, d_outcomeHolder, 1, -1);
}
protected SelectableTreatmentDefinitionsGraphModel buildRefinedAlternativesGraph() {
return new SelectableTreatmentDefinitionsGraphModel(getStudiesEndpointAndIndication(), d_refinedTreatmentDefinitions, d_outcomeHolder, 2, -1);
}
public ValueModel getRawSelectionCompleteModel() {
return getRawAlternativesGraph().getSelectionCompleteModel();
}
public ValueModel getRefinedSelectionCompleteModel() {
return getRefinedAlternativesGraph().getSelectionCompleteModel();
}
public ValueHolder<Boolean> getOverviewGraphConnectedModel() {
return d_selectedStudyGraphConnectedModel;
}
@SuppressWarnings("serial")
public class StudySelectionCompleteListener extends AbstractValueModel
implements ValueHolder<Boolean> {
private boolean d_value;
public StudySelectionCompleteListener() {
update();
getOverviewGraph().addGraphListener(new GraphListener<Vertex, Edge>() {
public void vertexRemoved(GraphVertexChangeEvent<Vertex> e) {
update();
}
public void vertexAdded(GraphVertexChangeEvent<Vertex> e) {
update();
}
public void edgeRemoved(GraphEdgeChangeEvent<Vertex, Edge> e) {
update();
}
public void edgeAdded(GraphEdgeChangeEvent<Vertex, Edge> e) {
update();
}
});
}
private void update() {
Boolean oldValue = d_value;
Boolean newValue = selectedStudiesConnected();
if (oldValue != newValue) {
d_value = newValue;
fireValueChange(oldValue, newValue);
}
}
public Boolean getValue() {
return d_value;
}
public void setValue(Object newValue) {
throw new RuntimeException();
}
}
private boolean selectedStudiesConnected() {
ConnectivityInspector<Vertex, Edge> inspectorGadget =
new ConnectivityInspector<Vertex, Edge>(getOverviewGraph());
return inspectorGadget.isGraphConnected();
}
@Override
public MetaAnalysis createAnalysis(String name) {
Indication indication = getIndicationModel().getValue();
OutcomeMeasure om = getOutcomeMeasureModel().getValue();
List<Study> studies = getSelectedStudies();
List<TreatmentDefinition> definitions = getSelectedRefinedTreatmentDefinitions();
Map<Study, Map<TreatmentDefinition, Arm>> armMap = getArmMap();
return new NetworkMetaAnalysis(name, indication, om, studies, definitions, armMap);
}
private Map<Study, Map<TreatmentDefinition, Arm>> getArmMap() {
Map<Study, Map<TreatmentDefinition, Arm>> map = new HashMap<Study, Map<TreatmentDefinition,Arm>>();
for (Study s : d_selectedArms.keySet()) {
map.put(s, new HashMap<TreatmentDefinition, Arm>());
for (TreatmentDefinition d : d_selectedArms.get(s).keySet()) {
map.get(s).put(d, d_selectedArms.get(s).get(d).getValue());
}
}
return map;
}
public ObservableList<Study> getSelectedStudies() {
return getSelectableStudyListPM().getSelectedStudiesModel();
}
public void rebuildOverviewGraph() {
d_overviewGraph.rebuildGraph();
}
public void populateSelectableStudies() {
if (d_armSelectionRebuildNeeded) {
d_selectableStudies.clear();
d_selectableStudies.addAll(filterStudiesComparing(getOutcomeMeasureModel().getValue(),
getStudiesEndpointAndIndication(), getRefinedAlternativesGraph().getSelectedDefinitions()));
}
}
private SelectableStudyCharTableModel createSelectableStudyListPm() {
SelectableStudyCharTableModel studyList = new SelectableStudyCharTableModel(
new StudyListPresentation(d_selectableStudies), d_pmf);
studyList.getSelectedStudiesModel().addListDataListener(new IndifferentListDataListener() {
protected void update() {
d_armSelectionRebuildNeeded = true;
}
});
return studyList;
}
private void updateOutcomes() {
SortedSet<OutcomeMeasure> outcomeSet = new TreeSet<OutcomeMeasure>();
if (d_indicationHolder.getValue() != null) {
for (Study s : d_domain.getStudies(d_indicationHolder.getValue())) {
outcomeSet.addAll(Study.extractVariables(s.getEndpoints()));
outcomeSet.addAll(Study.extractVariables(s.getAdverseEvents()));
}
}
d_outcomes.clear();
d_outcomes.addAll(outcomeSet);
}
private ObservableList<Study> createStudiesIndicationOutcome() {
final FilteredObservableList<Study> studies = new FilteredObservableList<Study>(d_domain.getStudies(), getIndicationOutcomeFilter());
PropertyChangeListener listener = new PropertyChangeListener() {
public void propertyChange(PropertyChangeEvent evt) {
studies.setFilter(getIndicationOutcomeFilter());
}
};
d_indicationHolder.addPropertyChangeListener(listener);
d_outcomeHolder.addPropertyChangeListener(listener);
return studies;
}
private Predicate<Study> getIndicationOutcomeFilter() {
if (d_indicationHolder.getValue() == null || d_outcomeHolder.getValue() == null) {
return new FalseFilter();
} else {
return new IndicationOutcomeFilter();
}
}
/**
* Return all studies that measure the selected endpoint on the selected indication for at least two drugs.
* @return List of studies
*/
protected ObservableList<Study> getStudiesEndpointAndIndication() {
return d_studiesEndpointIndication;
}
public ValueModel getStudiesMeasuringLabelModel() {
return d_studiesMeasuringValueModel;
}
public ObservableList<OutcomeMeasure> getAvailableOutcomeMeasures() {
return d_outcomes;
}
public ValueHolder<OutcomeMeasure> getOutcomeMeasureModel() {
return d_outcomeHolder;
}
public ObservableList<TreatmentDefinition> getAvailableRawTreatmentDefinitions() {
return d_rawTreatmentDefinitions;
}
public ObservableList<TreatmentDefinition> getAvailableRefinedTreatmentDefinitions() {
return d_refinedTreatmentDefinitions;
}
public boolean rebuildRefinedAlternativesGraph() {
SortedSet<TreatmentDefinition> definitions = new TreeSet<TreatmentDefinition>(permuteTreatmentDefinitions());
OutcomeMeasure om = getOutcomeMeasureModel().getValue();
List<Study> studies = filterStudiesComparing(om, getStudiesEndpointAndIndication(), definitions);
List<TreatmentDefinition> defs = filterDefinitionsMeasured(om, definitions, studies);
if (d_refinedTreatmentDefinitions.equals(defs)) {
return false;
}
d_refinedTreatmentDefinitions.clear();
d_refinedTreatmentDefinitions.addAll(defs);
d_refinedAlternativesGraph.rebuildGraph();
return true;
}
public boolean rebuildRawAlternativesGraph() {
if (!d_rebuildRawNeeded) {
return false;
}
// Determine set of treatment definitions
SortedSet<TreatmentDefinition> definitions = new TreeSet<TreatmentDefinition>();
if (d_indicationHolder.getValue() != null && d_outcomeHolder.getValue() != null) {
for (Study s : getStudiesEndpointAndIndication()) {
definitions.addAll(s.getMeasuredTreatmentDefinitions(d_outcomeHolder.getValue()));
}
}
d_rawTreatmentDefinitions.clear();
d_rawTreatmentDefinitions.addAll(definitions);
// Rebuild the graph
d_rawAlternativesGraph.rebuildGraph();
d_rebuildRawNeeded = false;
return true;
}
public SelectableTreatmentDefinitionsGraphModel getRefinedAlternativesGraph() {
return d_refinedAlternativesGraph;
}
public SelectableTreatmentDefinitionsGraphModel getRawAlternativesGraph() {
return d_rawAlternativesGraph;
}
public void rebuildArmSelection() {
if (!d_armSelectionRebuildNeeded) {
return;
}
d_selectedArms.clear();
for (Study s : getSelectableStudyListPM().getSelectedStudiesModel()) {
d_selectedArms.put(s, new HashMap<TreatmentDefinition, ModifiableHolder<Arm>>());
for (TreatmentDefinition d : getSelectedRefinedTreatmentDefinitions()) {
if (!s.getMeasuredArms(d_outcomeHolder.getValue(), d).isEmpty()) {
d_selectedArms.get(s).put(d, new ModifiableHolder<Arm>(getDefaultArm(s, d)));
}
}
}
d_armSelectionRebuildNeeded = false;
}
private Arm getDefaultArm(Study s, TreatmentDefinition d) {
return getArmsPerStudyPerDefinition(s, d).get(0);
}
public ObservableList<Arm> getArmsPerStudyPerDefinition(Study study, TreatmentDefinition definition) {
return study.getMeasuredArms(d_outcomeHolder.getValue(), definition);
}
public ModifiableHolder<Arm> getSelectedArmModel(Study study, TreatmentDefinition definition) {
return d_selectedArms.get(study).get(definition);
}
public SelectableStudyCharTableModel getSelectableStudyListPM() {
return d_selectableStudyListPm;
}
/**
* Get the set of drugs that occur in the selected raw treatment definitions.
* @see {@link #getSelectedRawTreatmentDefinitions()}
*/
public SortedSet<Drug> getSelectedDrugs() {
SortedSet<Drug> drugs = new TreeSet<Drug>();
for(TreatmentDefinition definition : getSelectedRawTreatmentDefinitions()) {
for(Category category : definition.getContents()) {
drugs.add(category.getDrug());
}
}
return drugs;
}
protected Set<TreatmentDefinition> permuteTreatmentDefinitions() {
Set<TreatmentDefinition> set = new HashSet<TreatmentDefinition>();
for (TreatmentDefinition trivial : getRawAlternativesGraph().getSelectedDefinitions()) {
// Find the categorizations relevant to the current combination of drugs
List<TreatmentCategorization> catzs = new ArrayList<TreatmentCategorization>();
List<Integer> nCat = new ArrayList<Integer>();
for (Category category : trivial.getContents()) {
TreatmentCategorization catz = (TreatmentCategorization) getCategorizationModel(category.getDrug()).getValue();
catzs.add(catz);
nCat.add(catz.getCategories().size());
}
// Generate all permutations of the categories in the relevant categorizations
int[] c = ArrayUtils.toPrimitive(nCat.toArray(new Integer[] {}));
int[] x = new int[c.length]; // initialized to { 0, ... }
do {
Set<Category> categories = new HashSet<Category>();
for (int i = 0; i < x.length; ++i) {
categories.add(catzs.get(i).getCategories().get(x[i]));
}
set.add(new TreatmentDefinition(categories));
} while (CollectionUtil.nextLexicographicElement(x, c));
}
return set;
}
public List<TreatmentCategorization> getAvailableCategorizations(Drug drug) {
return AffixedObservableList.createPrefixed(d_domain.getCategorizations(drug), TreatmentCategorization.createTrivial(drug));
}
public ValueModel getCategorizationModel(Drug drug) {
if(d_selectedCategorizations.get(drug) == null) {
d_selectedCategorizations.put(drug, new ModifiableHolder<TreatmentCategorization>(TreatmentCategorization.createTrivial(drug)));
}
return d_selectedCategorizations.get(drug);
}
final class FalseFilter implements Predicate<Study> {
public boolean evaluate(Study s) {
return false;
}
}
final class IndicationOutcomeFilter implements Predicate<Study> {
public boolean evaluate(Study s) {
return s.getIndication().equals(d_indicationHolder.getValue()) &&
s.getOutcomeMeasures().contains(d_outcomeHolder.getValue()) &&
s.getMeasuredTreatmentDefinitions(d_outcomeHolder.getValue()).size() >= 2;
}
}
@SuppressWarnings("serial")
public class StudiesMeasuringValueModel extends AbstractValueModel implements PropertyChangeListener {
public StudiesMeasuringValueModel() {
d_outcomeHolder.addValueChangeListener(this);
}
public Object getValue() {
return constructString();
}
private Object constructString() {
String indVal = d_indicationHolder.getValue() != null ? d_indicationHolder.getValue().toString() : "";
String endpVal = d_outcomeHolder.getValue() != null ? d_outcomeHolder.getValue().toString() : "";
return "Studies measuring " + indVal + " on " + endpVal;
}
public void setValue(Object newValue) {
throw new RuntimeException("value set not allowed");
}
public void propertyChange(PropertyChangeEvent arg0) {
fireValueChange(null, constructString());
}
}
/**
* Internal utility for tests. This will reset all the graph states and usage is prohibited in the GUI.
*/
protected void rebuildAllGraphs() {
rebuildRawAlternativesGraph();
permuteTreatmentDefinitions();
rebuildRefinedAlternativesGraph();
rebuildOverviewGraph();
}
public String getRawDescription() {
return String.format(getDescriptionTemplate(), "drugs", "one drug");
}
public String getRefinedDescription() {
return String.format(getDescriptionTemplate(), "treatment definitions", "two definitions");
}
protected String getDescriptionTemplate() {
return TEMPLATE_DEFINITIONS_DESCRIPTION;
}
}