org.openmrs.cohort.CohortSearchHistory.java Source code

Java tutorial

Introduction

Here is the source code for org.openmrs.cohort.CohortSearchHistory.java

Source

/**
 * 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 static final Log log = LogFactory.getLog(CohortSearchHistory.class);

    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(OpenmrsUtil.toPatientFilter(ps, this));
        // the potentially-expensive query should be done lazily
        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;
        synchronized (this) {
            if (useCache) {
                ret = cachedResults.get(i);
            }
            if (ret == null) {
                ensureCachedFilter(i);
                PatientFilter pf = cachedFilters.get(i);
                ret = pf.filter(null, 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;
    }

}