/*
* (c) 2008- RANDI2 Core Development Team
*
* This file is part of RANDI2.
*
* RANDI2 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.
*
* RANDI2 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
* RANDI2. If not, see <http://www.gnu.org/licenses/>.
*/
package de.randi2.model;
import static de.randi2.utility.ArithmeticUtil.cartesianProduct;
import java.io.File;
import java.io.Serializable;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.GregorianCalendar;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import javax.persistence.CascadeType;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.FetchType;
import javax.persistence.Lob;
import javax.persistence.ManyToMany;
import javax.persistence.ManyToOne;
import javax.persistence.NamedQuery;
import javax.persistence.OneToMany;
import javax.persistence.OneToOne;
import javax.persistence.Transient;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
import lombok.EqualsAndHashCode;
import lombok.Getter;
import lombok.Setter;
import lombok.ToString;
import org.hibernate.validator.constraints.NotEmpty;
import org.springframework.beans.factory.annotation.Configurable;
import de.randi2.model.criteria.AbstractCriterion;
import de.randi2.model.criteria.DichotomousCriterion;
import de.randi2.model.criteria.constraints.AbstractConstraint;
import de.randi2.model.enumerations.TrialStatus;
import de.randi2.model.randomization.AbstractRandomizationConfig;
import de.randi2.utility.Pair;
import de.randi2.utility.StrataNameIDWrapper;
import de.randi2.utility.validations.DateDependence;
/**
* The Class Trial.
*/
@Entity
@Configurable
@DateDependence(firstDate = "startDate", secondDate = "endDate")
@EqualsAndHashCode(callSuper = true, exclude = { "randomConf",
"participatingSites", "sponsorInvestigator", "subjectCriteria" })
@NamedQuery(name = "trial.AllTrialsWithSpecificParticipatingTrialSite", query = "select trial from Trial as trial join trial.participatingSites site where site.id = ?")
@ToString(callSuper=true, of={"name", "abbreviation", "status", "startDate", "endDate"})
public class Trial extends AbstractDomainObject {
public static final Comparator<TrialSubject> SUBJECT_COUNT_COMPERATOR = new Comparator<TrialSubject>() {
/**
* {@inheritDoc}
*/
@Override
public int compare(TrialSubject o1, TrialSubject o2) {
return (o1.getCounter() - o2.getCounter());
}
};
/** The Constant serialVersionUID. */
private static final long serialVersionUID = -2424750074810584832L;
/** The name. */
@NotNull()
@NotEmpty()
@Size(max = MAX_VARCHAR_LENGTH)
@Getter
@Setter
private String name = "";
/** The abbreviation. */
@Size(max = MAX_VARCHAR_LENGTH)
@Getter
@Setter
private String abbreviation = "";
/**
* Checks if is stratify trial site.
*
* @return true, if is stratify trial site
*/
@Getter
@Setter
private boolean stratifyTrialSite;
/** The description. */
@Lob
@Getter
@Setter
private String description = "";
@Getter
@Setter
private GregorianCalendar startDate = null;
@Getter
@Setter
private GregorianCalendar endDate = null;
@Getter
@Setter
private File protocol = null;
/** The sponsor investigator. */
@NotNull
@ManyToOne
@Getter
@Setter
private Person sponsorInvestigator = null;
/** The leading site. */
@NotNull
@ManyToOne
@Getter
@Setter
private TrialSite leadingSite = null;
/** The status. */
@Enumerated(value = EnumType.STRING)
@NotNull
@Getter
@Setter
private TrialStatus status = TrialStatus.IN_PREPARATION;
/** The participating sites. */
@ManyToMany(fetch=FetchType.EAGER)
@Getter
@Setter
private Set<TrialSite> participatingSites = new HashSet<TrialSite>();
/** The treatment arms. */
@OneToMany(cascade = CascadeType.ALL, mappedBy = "trial", fetch=FetchType.EAGER)
@Getter
@Setter
private Set<TreatmentArm> treatmentArms = new HashSet<TreatmentArm>();
/** The subject criteria. */
@OneToMany(cascade = CascadeType.ALL)
private List<AbstractCriterion<? extends Serializable, ? extends AbstractConstraint<? extends Serializable>>> subjectCriteria = new ArrayList<AbstractCriterion<? extends Serializable, ? extends AbstractConstraint<? extends Serializable>>>();
/** The treatment response criteria. */
@OneToOne(cascade = CascadeType.ALL)
@Getter
@Setter
private DichotomousCriterion treatmentResponse;
/** The random conf. */
@OneToOne(cascade = CascadeType.ALL, fetch=FetchType.EAGER)
private AbstractRandomizationConfig randomConf;
/**
* If true then the trial subject ids will be generated automatically by the
* system.
*/
@Getter
@Setter
private boolean generateIds = true;
/**
* Get criteria.
*
* @return the criteria
*/
public List<AbstractCriterion<? extends Serializable, ? extends AbstractConstraint<? extends Serializable>>> getCriteria() {
return subjectCriteria;
}
/**
* Set criteria.
*
* @param inclusionCriteria
* the inclusion criteria
*/
public void setCriteria(
List<AbstractCriterion<? extends Serializable, ? extends AbstractConstraint<? extends Serializable>>> inclusionCriteria) {
this.subjectCriteria = inclusionCriteria;
}
/**
* Adds the criterion, if the criterion is equals null nothing happens.
*
* @param criterion
* the criterion
*/
public void addCriterion(
AbstractCriterion<? extends Serializable, ? extends AbstractConstraint<? extends Serializable>> criterion) {
if(criterion != null)
this.subjectCriteria.add(criterion);
}
/**
* Adds a participating site to this trial and added this Trial to the participating site.
*
* @param participatingSite
* the participating site
*/
public void addParticipatingSite(TrialSite participatingSite) {
if(participatingSite!=null){
this.participatingSites.add(participatingSite);
if(!participatingSite.getTrials().contains(this)){
participatingSite.getTrials().add(this);
}
}
}
/**
* Gets the randomization configuration.
*
* @return the randomization configuration
*/
public AbstractRandomizationConfig getRandomizationConfiguration() {
if(randomConf != null && randomConf.getTrial() == null){
randomConf.setTrial(this);
}
return randomConf;
}
/**
* Sets the randomization configuration. If the trial object of the
* randomisation config is equals null the setter set the this trial.
*
* @param _randomizationConfiguration
* the new randomization configuration
*/
public void setRandomizationConfiguration(
AbstractRandomizationConfig _randomizationConfiguration) {
randomConf = _randomizationConfiguration;
if (randomConf != null && randomConf.getTrial() == null){
randomConf.setTrial(this);
}
}
/**
* Gets all subjects from this trial.
*
* @return the subjects
*/
@Transient
public List<TrialSubject> getSubjects() {
List<TrialSubject> subjects = new ArrayList<TrialSubject>();
for (TreatmentArm arm : treatmentArms) {
subjects.addAll(arm.getSubjects());
}
Collections.sort(subjects, SUBJECT_COUNT_COMPERATOR);
return subjects;
}
/**
* Gets the total subject amount.
*
* @return the total subject amount
*/
@Transient
public int getTotalSubjectAmount() {
return getSubjects().size();
}
@Transient
public int getPlannedSubjectAmount() {
int amount = 0;
for (TreatmentArm arm : treatmentArms) {
amount += arm.getPlannedSubjects();
}
return amount;
}
/**
* Specifies if the trial is a fresh trial (without any subjects)
*
* @return
*/
@Transient
public boolean isFresh() {
return !(getTotalSubjectAmount() > 0);
}
/*
* (non-Javadoc)
*
* @see de.randi2.model.AbstractDomainObject#getUIName()
*/
@Override
public String getUIName() {
return this.getAbbreviation();
}
@SuppressWarnings("unchecked")
public Pair<List<String>, List<String>> getAllStrataIdsAndNames() {
List<String> strataIdsResult = new ArrayList<String>();
List<String> strataNamesResult = new ArrayList<String>();
HashMap<AbstractCriterion<?, ?>, List<AbstractConstraint<?>>> temp = new HashMap<AbstractCriterion<?, ?>, List<AbstractConstraint<?>>>();
for (AbstractCriterion<?, ?> cr : getCriteria()) {
List<AbstractConstraint<?>> list = new ArrayList<AbstractConstraint<?>>();
for (AbstractConstraint<?> co : cr.getStrata()) {
list.add(co);
}
temp.put(cr, list);
}
Set<Set<StrataNameIDWrapper>> strataIds = new HashSet<Set<StrataNameIDWrapper>>();
// minimum one constraint
if (temp.size() >= 1) {
for (AbstractCriterion<?, ?> cr : temp.keySet()) {
Set<StrataNameIDWrapper> strataLevel = new HashSet<StrataNameIDWrapper>();
for (AbstractConstraint<?> co : temp.get(cr)) {
StrataNameIDWrapper wrapper = new StrataNameIDWrapper();
wrapper.setStrataId(cr.getId() + "_" + co.getId());
wrapper.setStrataName(cr.getName() + "_" + co.getUIName());
strataLevel.add(wrapper);
}
if (temp.get(cr).isEmpty()) {
StrataNameIDWrapper wrapper = new StrataNameIDWrapper();
wrapper.setStrataId(cr.getId() + "_" + -1);
wrapper.setStrataName("");
strataLevel.add(wrapper);
}
if (!strataLevel.isEmpty()) {
strataIds.add(strataLevel);
}
}
// cartesianProduct only necessary for more then one criterions
if (strataIds.size() >= 2) {
strataIds = cartesianProduct(strataIds.toArray(new HashSet[0]));
} else {
Set<StrataNameIDWrapper> tempStrataIds = strataIds.iterator()
.next();
Set<Set<StrataNameIDWrapper>> tempStrataIdsSet = new HashSet<Set<StrataNameIDWrapper>>();
for (StrataNameIDWrapper wrapper : tempStrataIds) {
Set<StrataNameIDWrapper> next = new HashSet<StrataNameIDWrapper>();
next.add(wrapper);
tempStrataIdsSet.add(next);
}
strataIds = tempStrataIdsSet;
}
for (Set<StrataNameIDWrapper> set : strataIds) {
List<StrataNameIDWrapper> stringStrat = new ArrayList<StrataNameIDWrapper>();
for (StrataNameIDWrapper string : set) {
stringStrat.add(string);
}
Collections.sort(stringStrat);
String stratId = "";
String stratName = "";
for (StrataNameIDWrapper s : stringStrat) {
stratId += s.getStrataId() + ";";
if (!s.getStrataName().isEmpty())
stratName += s.getStrataName() + ";";
}
// strata and stratified with trial site
if (isStratifyTrialSite()) {
for (TrialSite site : getParticipatingSites()) {
String strataId = site.getId() + "__" + stratId;
strataIdsResult.add(strataId);
strataNamesResult.add(site.getName() + " | "
+ stratName);
}
}
// strata and stratified without trial site
else {
strataIdsResult.add(stratId);
strataNamesResult.add(stratName);
}
}
} else if (isStratifyTrialSite()) { // stratified only by trial site
for (TrialSite site : getParticipatingSites()) {
String strataId = site.getId() + "__";
strataIdsResult.add(strataId);
strataNamesResult.add(site.getName());
}
}
return Pair.of(strataIdsResult, strataNamesResult);
}
}