/**
* The contents of this file are subject to the OpenMRS Public License
* Version 1.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://license.openmrs.org
*
* Software distributed under the License is distributed on an "AS IS"
* basis, WITHOUT WARRANTY OF ANY KIND, either express or implied. See the
* License for the specific language governing rights and limitations
* under the License.
*
* Copyright (C) OpenMRS, LLC. All Rights Reserved.
*/
package org.openmrs.cohort;
import java.io.StreamTokenizer;
import java.io.StringReader;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.Stack;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.openmrs.Cohort;
import org.openmrs.api.PatientSetService;
import org.openmrs.api.PatientSetService.BooleanOperator;
import org.openmrs.api.context.Context;
import org.openmrs.report.EvaluationContext;
import org.openmrs.reporting.AbstractReportObject;
import org.openmrs.reporting.PatientFilter;
import org.openmrs.reporting.PatientSearch;
import org.openmrs.reporting.ReportObject;
import org.openmrs.util.OpenmrsUtil;
/**
* @deprecated see reportingcompatibility module
*/
@Deprecated
public class CohortSearchHistory extends AbstractReportObject {
protected transient final Log log = LogFactory.getLog(getClass());
public class CohortSearchHistoryItemHolder {
private PatientSearch search;
private PatientFilter filter;
private String name;
private String description;
private Boolean saved;
private Cohort cachedResult;
private Date cachedResultDate;
public CohortSearchHistoryItemHolder() {
}
public Cohort getCachedResult() {
return cachedResult;
}
public void setCachedResult(Cohort cachedResult) {
this.cachedResult = cachedResult;
}
public Date getCachedResultDate() {
return cachedResultDate;
}
public void setCachedResultDate(Date cachedResultDate) {
this.cachedResultDate = cachedResultDate;
}
public PatientSearch getSearch() {
return search;
}
public void setSearch(PatientSearch search) {
this.search = search;
}
public PatientFilter getFilter() {
return filter;
}
public void setFilter(PatientFilter filter) {
this.filter = filter;
}
public String getDescription() {
return description;
}
public void setDescription(String description) {
this.description = description;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public Boolean getSaved() {
return saved;
}
public void setSaved(Boolean saved) {
this.saved = saved;
}
}
private List<PatientSearch> searchHistory;
private volatile List<PatientFilter> cachedFilters;
private volatile List<Cohort> cachedResults;
private volatile List<Date> cachedResultDates;
public CohortSearchHistory() {
super.setType("Search History");
super.setSubType("Search History");
searchHistory = new ArrayList<PatientSearch>();
cachedFilters = new ArrayList<PatientFilter>();
cachedResults = new ArrayList<Cohort>();
cachedResultDates = new ArrayList<Date>();
}
public synchronized List<CohortSearchHistoryItemHolder> getItems() {
checkArrayLengths();
List<CohortSearchHistoryItemHolder> ret = new ArrayList<CohortSearchHistoryItemHolder>();
for (int i = 0; i < searchHistory.size(); ++i) {
CohortSearchHistoryItemHolder item = new CohortSearchHistoryItemHolder();
PatientSearch search = searchHistory.get(i);
item.setSearch(search);
ensureCachedFilter(i);
PatientFilter filter = cachedFilters.get(i);
item.setFilter(filter);
if (search.isSavedFilterReference()) {
ReportObject ro = Context.getReportObjectService().getReportObject(search.getSavedFilterId());
item.setName(ro.getName());
item.setDescription(ro.getDescription());
} else if (search.isSavedCohortReference()) {
org.openmrs.Cohort c = Context.getCohortService().getCohort(search.getSavedCohortId());
item.setName(c.getName());
item.setDescription(c.getDescription());
} else if (search.isSavedSearchReference()) {
ReportObject ro = Context.getReportObjectService().getReportObject(search.getSavedSearchId());
item.setName(ro.getName());
item.setDescription(ro.getDescription());
} else if (search.isComposition()) {
item.setName(search.getCompositionString());
} else {
item.setName(filter.getName());
item.setDescription(filter.getDescription());
}
item.setSaved(search.isSavedReference());
item.setCachedResult(cachedResults.get(i));
item.setCachedResultDate(cachedResultDates.get(i));
ret.add(item);
}
return ret;
}
public List<PatientSearch> getSearchHistory() {
return searchHistory;
}
public void setSearchHistory(List<PatientSearch> searchHistory) {
this.searchHistory = searchHistory;
cachedFilters = new ArrayList<PatientFilter>();
cachedResults = new ArrayList<Cohort>();
cachedResultDates = new ArrayList<Date>();
for (int i = 0; i < searchHistory.size(); ++i) {
cachedFilters.add(null);
cachedResults.add(null);
cachedResultDates.add(null);
}
}
public List<PatientFilter> getCachedFilters() {
return cachedFilters;
}
public List<Date> getCachedResultDates() {
return cachedResultDates;
}
public List<Cohort> getCachedResults() {
return cachedResults;
}
public int size() {
return searchHistory.size();
}
public int getSize() {
return size();
}
public synchronized void addSearchItem(PatientSearch ps) {
checkArrayLengths();
searchHistory.add(ps);
cachedFilters.add(null);
cachedResults.add(null);
cachedResultDates.add(null);
}
public synchronized void removeSearchItem(int i) {
checkArrayLengths();
List<Integer> toDelete = new ArrayList<Integer>();
toDelete.add(i);
while (toDelete.size() > 0) {
int index = toDelete.remove(0);
List<Integer> toCascade = removeSearchItemHelper(index);
searchHistory.remove(index);
cachedFilters.remove(index);
cachedResults.remove(index);
cachedResultDates.remove(index);
toDelete.addAll(toCascade);
}
}
/**
* @return zero-based indices that should also be removed due to cascading. (These will already
* have had 1 subtracted from them, since we know that a search from above is being
* deleted)
*/
private synchronized List<Integer> removeSearchItemHelper(int i) {
// 1. Decrement any number in a CohortHistoryCompositionFilter that's greater than i.
// 2. If any CohortHistoryCompositionFilter references search i, we'll have to cascade delete it
List<Integer> ret = new ArrayList<Integer>();
for (int j = i + 1; j < searchHistory.size(); ++j) {
PatientSearch ps = searchHistory.get(j);
if (ps.isComposition()) {
cachedFilters.set(i, null); // this actually only needs to happen if the filter is affected
// note that i is zero-based, but in a composition filter it would be one-based
boolean removeMeToo = ps.removeFromHistoryNotify(i + 1);
if (removeMeToo)
ret.add(j - 1);
}
}
return ret;
}
public synchronized PatientFilter ensureCachedFilter(int i) {
if (cachedFilters.get(i) == null)
cachedFilters.set(i, OpenmrsUtil.toPatientFilter(searchHistory.get(i), this));
return cachedFilters.get(i);
}
/**
* @param i
* @return patient set resulting from the i_th filter in the search history. (cached if
* possible)
*/
public Cohort getPatientSet(int i, EvaluationContext context) {
return getPatientSet(i, true, context);
}
/**
* TODO: Implement {@link org.openmrs.api.impl.CohortServiceImpl#getAllCohorts()}
*
* @param i
* @param useCache whether to use a cached result, if available
* @return patient set resulting from the i_th filter in the search history
*/
public Cohort getPatientSet(int i, boolean useCache, EvaluationContext context) {
checkArrayLengths();
Cohort ret = null;
if (useCache) {
ret = cachedResults.get(i);
}
if (ret == null) {
ensureCachedFilter(i);
PatientFilter pf = cachedFilters.get(i);
Cohort everyone = Context.getPatientSetService().getAllPatients();
ret = pf.filter(everyone, context);
cachedFilters.set(i, pf);
cachedResults.set(i, ret);
cachedResultDates.set(i, new Date());
}
return ret;
}
public Cohort getLastPatientSet(EvaluationContext context) {
if (searchHistory.size() > 0)
return getPatientSet(searchHistory.size() - 1, context);
else
return new Cohort();
}
public Cohort getPatientSetCombineWithAnd(EvaluationContext context) {
Set<Integer> current = null;
for (int i = 0; i < searchHistory.size(); ++i) {
Cohort ps = getPatientSet(i, context);
if (current == null)
current = new HashSet<Integer>(ps.getMemberIds());
else
current.retainAll(ps.getMemberIds());
}
if (current == null)
return Context.getPatientSetService().getAllPatients();
else {
return new Cohort("Cohort anded together", "", current);
}
}
public Cohort getPatientSetCombineWithOr(EvaluationContext context) {
Set<Integer> ret = new HashSet<Integer>();
for (int i = 0; i < searchHistory.size(); ++i) {
ret.addAll(getPatientSet(i, context).getMemberIds());
}
return new Cohort("Cohort or'd together", "", ret);
}
// Just in case someone has modified the searchHistory list directly. Maybe I should make that getter return an unmodifiable list.
// TODO: this isn't actually good enough. Use the unmodifiable list method instead
private synchronized void checkArrayLengths() {
int n = searchHistory.size();
while (cachedFilters.size() > n)
cachedFilters.remove(n);
while (cachedResults.size() > n)
cachedResults.remove(n);
while (cachedResultDates.size() > n)
cachedResultDates.remove(n);
while (cachedFilters.size() < n)
cachedFilters.add(null);
while (cachedResults.size() < n)
cachedResults.add(null);
while (cachedResultDates.size() < n)
cachedResultDates.add(null);
}
public PatientSearch createCompositionFilter(String description) {
Set<String> andWords = new HashSet<String>();
Set<String> orWords = new HashSet<String>();
Set<String> notWords = new HashSet<String>();
andWords.add("and");
andWords.add("intersection");
andWords.add("*");
orWords.add("or");
orWords.add("union");
orWords.add("+");
notWords.add("not");
notWords.add("!");
List<Object> currentLine = new ArrayList<Object>();
try {
StreamTokenizer st = new StreamTokenizer(new StringReader(description));
st.ordinaryChar('(');
st.ordinaryChar(')');
Stack<List<Object>> stack = new Stack<List<Object>>();
while (st.nextToken() != StreamTokenizer.TT_EOF) {
if (st.ttype == StreamTokenizer.TT_NUMBER) {
Integer thisInt = new Integer((int) st.nval);
if (thisInt < 1 || thisInt > searchHistory.size()) {
log.error("number < 1 or > search history size");
return null;
}
currentLine.add(thisInt);
} else if (st.ttype == '(') {
stack.push(currentLine);
currentLine = new ArrayList<Object>();
} else if (st.ttype == ')') {
List<Object> l = stack.pop();
l.add(currentLine);
currentLine = l;
} else if (st.ttype == StreamTokenizer.TT_WORD) {
String str = st.sval.toLowerCase();
if (andWords.contains(str))
currentLine.add(PatientSetService.BooleanOperator.AND);
else if (orWords.contains(str))
currentLine.add(PatientSetService.BooleanOperator.OR);
else if (notWords.contains(str))
currentLine.add(PatientSetService.BooleanOperator.NOT);
else
throw new IllegalArgumentException("Don't recognize " + st.sval);
}
}
}
catch (Exception ex) {
log.error("Error in description string: " + description, ex);
return null;
}
if (!testCompositionList(currentLine)) {
log.error("Description string failed test: " + description);
return null;
}
//return toPatientFilter(currentLine);
PatientSearch ret = new PatientSearch();
ret.setParsedComposition(currentLine);
return ret;
}
@SuppressWarnings("unchecked")
private static boolean testCompositionList(List<Object> list) {
// if length > 2, make sure there's at least one operator
// make sure NOT is always followed by something
// make sure not everything is a logical operator
// can't have two logical operators in a row (unless the second is a NOT)
boolean anyNonOperator = false;
boolean anyOperator = false;
boolean lastIsNot = false;
boolean lastIsOperator = false;
boolean childrenOkay = true;
for (Object o : list) {
if (o instanceof List) {
childrenOkay &= testCompositionList((List<Object>) o);
anyNonOperator = true;
} else if (o instanceof BooleanOperator) {
if (lastIsOperator && (BooleanOperator) o != BooleanOperator.NOT)
return false;
anyOperator = true;
} else if (o instanceof Integer) {
anyNonOperator = true;
} else {
throw new RuntimeException("Programming error! unexpected class " + o.getClass());
}
lastIsNot = ((o instanceof BooleanOperator) && (((BooleanOperator) o) == BooleanOperator.NOT));
lastIsOperator = o instanceof BooleanOperator;
}
if (list.size() > 2 && !anyOperator)
return false;
if (lastIsNot)
return false;
if (!anyNonOperator)
return false;
return true;
}
}