package org.activityinfo.legacy.shared.command; /* * #%L * ActivityInfo Server * %% * Copyright (C) 2009 - 2013 UNICEF * %% * 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/gpl-3.0.html>. * #L% */ import com.google.common.collect.Sets; import org.activityinfo.legacy.shared.reports.model.DateRange; import org.activityinfo.legacy.shared.reports.model.typeadapter.FilterAdapter; import javax.xml.bind.annotation.XmlElement; import javax.xml.bind.annotation.XmlTransient; import javax.xml.bind.annotation.adapters.XmlJavaTypeAdapter; import java.io.Serializable; import java.util.*; import java.util.Map.Entry; /** * Defines a filter of activity data as a date range and a set of restrictions * on <code>Dimensions</code>. */ @XmlJavaTypeAdapter(FilterAdapter.class) public class Filter implements Serializable { private static final long serialVersionUID = -9117480720562024905L; // TODO: should be restrictions on DIMENSIONS and not DimensionTypes!! private Map<DimensionType, Set<Integer>> restrictions = new HashMap<DimensionType, Set<Integer>>(); private DateRange dateRange = new DateRange(); /** * In short: when set to true, the where-clauses of the pivotquery are joined by 'OR' instead of 'AND' (which is the * default). */ private boolean lenient = false; /** * Constructs a <code>Filter</code> with no restrictions. All data visible * to the user will be included. */ public Filter() { } public Filter(boolean lenient) { this.lenient = lenient; } /** * Constructs a copy of the given <code>filter</code> * * @param filter The filter which to copy. */ public Filter(Filter filter) { for (Map.Entry<DimensionType, Set<Integer>> entry : filter.restrictions.entrySet()) { this.restrictions.put(entry.getKey(), new HashSet<Integer>(entry.getValue())); } this.dateRange = filter.dateRange; this.lenient = filter.lenient; } /** * Constructs a <code>Filter</code> as the intersection between two * <code>Filter</code>s. * * @param a The first filter * @param b The second filter */ public Filter(Filter a, Filter b) { Set<DimensionType> types = new HashSet<DimensionType>(); types.addAll(a.restrictions.keySet()); types.addAll(b.restrictions.keySet()); for (DimensionType type : types) { this.restrictions.put(type, intersect(a.getRestrictionSet(type, false), b.getRestrictionSet(type, false))); } this.dateRange = DateRange.intersection(a.getDateRange(), b.getDateRange()); } private Set<Integer> intersect(Set<Integer> a, Set<Integer> b) { if (a.size() == 0) { return new HashSet<Integer>(b); } if (b.size() == 0) { return new HashSet<Integer>(a); } Set<Integer> intersection = new HashSet<Integer>(a); intersection.retainAll(b); return intersection; } public Set<Integer> getRestrictions(DimensionType type) { return getRestrictionSet(type, false); } private Set<Integer> getRestrictionSet(DimensionType type, boolean create) { Set<Integer> set = restrictions.get(type); if (set == null) { if (!create) { return Collections.emptySet(); } set = new HashSet<Integer>(); restrictions.put(type, set); } return set; } public void addRestriction(DimensionType type, int categoryId) { Set<Integer> set = getRestrictionSet(type, true); set.add(categoryId); } public void addRestriction(DimensionType type, Collection<Integer> categoryIds) { if (!categoryIds.isEmpty()) { Set<Integer> set = getRestrictionSet(type, true); set.addAll(categoryIds); } } public void clearRestrictions(DimensionType type) { restrictions.remove(type); } public boolean isRestricted(DimensionType type) { if (type == DimensionType.Date) { return isDateRestricted(); } else { Set<Integer> set = restrictions.get(type); return set != null && !set.isEmpty(); } } public boolean isNull() { return restrictions.isEmpty() && !isDateRestricted(); } public boolean hasRestrictions() { return !restrictions.isEmpty(); } public boolean isDateRestricted() { if (dateRange == null) { return false; } return dateRange.getMinDate() != null || dateRange.getMaxDate() != null; } public Set<DimensionType> getRestrictedDimensions() { Set<DimensionType> dims = Sets.newHashSet(); for (Entry<DimensionType, Set<Integer>> entries : restrictions.entrySet()) { if (!entries.getValue().isEmpty()) { dims.add(entries.getKey()); } } return dims; } public Map<DimensionType, Set<Integer>> getRestrictions() { return restrictions; } @XmlTransient public Date getMinDate() { return getDateRange().getMinDate(); } public void setMinDate(Date minDate) { getDateRange().setMinDate(minDate); } @XmlTransient public Date getMaxDate() { return getDateRange().getMaxDate(); } public void setMaxDate(Date maxDate) { getDateRange().setMaxDate(maxDate); } public void setDateRange(DateRange range) { this.dateRange = range; } public boolean isDimensionRestrictedToSingleCategory(DimensionType type) { return getRestrictions(type).size() == 1; } /** * @return * @throws UnsupportedOperationException if the dimension is not restricted to exactly one category */ public int getRestrictedCategory(DimensionType type) { Set<Integer> ids = getRestrictions(type); if (ids.size() != 1) { throw new UnsupportedOperationException( "Cannot return a unique category, the dimension " + type + " is restricted to " + ids.size() + " categories"); } return ids.iterator().next(); } @XmlElement public DateRange getDateRange() { if (dateRange == null) { dateRange = new DateRange(); } return dateRange; } public boolean isLenient() { return lenient; } public void setLenient(boolean lenient) { this.lenient = lenient; } @Override public String toString() { StringBuilder sb = new StringBuilder(); for (DimensionType type : getRestrictedDimensions()) { if (sb.length() != 0) { sb.append(", "); } sb.append(type.toString()).append("={"); for (Integer id : getRestrictions(type)) { sb.append(' ').append(id); } sb.append(" }"); } if (dateRange.getMinDate() != null || dateRange.getMaxDate() != null) { if (sb.length() != 0) { sb.append(", "); } sb.append("date=["); if (dateRange.getMinDate() != null) { sb.append(dateRange.getMinDate()); } sb.append(","); if (dateRange.getMaxDate() != null) { sb.append(dateRange.getMaxDate()).append("]"); } } if (sb.length() != 0) { sb.append(", "); } sb.append("lenient: "); sb.append(lenient); sb.insert(0, "["); sb.append("]"); return sb.toString(); } public Filter onActivity(int activityId) { addRestriction(DimensionType.Activity, activityId); return this; } public Filter onSite(int siteId) { addRestriction(DimensionType.Site, siteId); return this; } public Filter onDatabase(int databaseId) { addRestriction(DimensionType.Database, databaseId); return this; } public static Filter filter() { return new Filter(); } @Override public int hashCode() { final int prime = 31; int result = 1; result = prime * result + ((dateRange == null) ? 0 : dateRange.hashCode()); result = prime * result + ((restrictions == null) ? 0 : restrictions.hashCode()); return result; } @Override public boolean equals(Object obj) { if (this == obj) { return true; } if (obj == null) { return false; } if (getClass() != obj.getClass()) { return false; } Filter other = (Filter) obj; return getDateRange().equals(other.getDateRange()) && getRestrictions().equals(other.getRestrictions()); } }