Skip to content

Commit

Permalink
#104 QuerySpec in-memory filtering fixes (#199)
Browse files Browse the repository at this point in the history
- proper total count computation
- support to traverse multi-valued properties
  • Loading branch information
Remo authored Nov 6, 2016
1 parent 82dbedd commit 82aebc1
Show file tree
Hide file tree
Showing 4 changed files with 225 additions and 146 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package io.katharsis.queryspec;

import java.util.Collection;

import io.katharsis.utils.CompareUtils;

/**
Expand All @@ -18,7 +20,7 @@ public boolean matches(Object value1, Object value2) {
}

};

/**
* like operation
*/
Expand All @@ -31,7 +33,6 @@ public boolean matches(Object value1, Object value2) {

};


/**
* Boolean or
*/
Expand Down Expand Up @@ -63,6 +64,9 @@ public boolean matches(Object value1, Object value2) {

@Override
public boolean matches(Object value1, Object value2) {
if (value2 instanceof Collection) {
return ((Collection<?>) value2).contains(value1);
}
return CompareUtils.isEquals(value1, value2);
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,141 +1,164 @@
package io.katharsis.queryspec;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import io.katharsis.response.paging.PagedResultList;
import io.katharsis.utils.PropertyUtils;

/**
* Applies the given QuerySpec to the provided list in memory. Result available
* with getResult(). Use QuerySpec.apply to make use of this class.
*/
public class InMemoryEvaluator {

public <T> List<T> eval(Iterable<T> resources, QuerySpec querySpec) {
List<T> results = new ArrayList<>();

Iterator<T> iterator = resources.iterator();
while(iterator.hasNext()){
results.add(iterator.next());
}
long totalCount = results.size();

// filter
if (!querySpec.getFilters().isEmpty()) {
FilterSpec filterSpec = FilterSpec.and(querySpec.getFilters());
applyFilter(results, filterSpec);
}

// sort
applySorting(results, querySpec.getSort());

// offset/limit
results = applyPaging(results, querySpec);

return new PagedResultList<>(results, totalCount);
}

private <T> void applySorting(List<T> results, List<SortSpec> sortSpec) {
if (!sortSpec.isEmpty()) {
Collections.sort(results, new SortSpecComparator<>(sortSpec));
}
}

private <T> List<T> applyPaging(List<T> results, QuerySpec querySpec) {
int offset = (int) Math.min(querySpec.getOffset(), Integer.MAX_VALUE);
int limit = (int) Math.min(Integer.MAX_VALUE,
querySpec.getLimit() != null ? querySpec.getLimit() : Integer.MAX_VALUE);
limit = Math.min(results.size() - offset, limit);
if (offset > 0 || limit < results.size()) {
return results.subList(offset, offset + limit);
}
return results;
}

private <T> void applyFilter(List<T> results, FilterSpec filterSpec) {
if (filterSpec != null) {
Iterator<T> iterator = results.iterator();
while (iterator.hasNext()) {
if (!matches(iterator.next(), filterSpec)) {
iterator.remove();
}
}
}
}

public static boolean matches(Object object, FilterSpec filterSpec) {
List<FilterSpec> expressions = filterSpec.getExpression();
if (expressions == null) {
Object value = PropertyUtils.getProperty(object, filterSpec.getAttributePath());
return filterSpec.getOperator().matches(value, filterSpec.getValue());
} else if (filterSpec.getOperator() == FilterOperator.OR) {
return matchesOr(object, expressions);
} else if (filterSpec.getOperator() == FilterOperator.AND) {
return matchesAnd(object, expressions);
} else if (filterSpec.getOperator() == FilterOperator.NOT) {
return !matches(object, FilterSpec.and(expressions));
}
throw new UnsupportedOperationException("not implemented " + filterSpec);
}

private static boolean matchesOr(Object object, List<FilterSpec> expressions) {
for (FilterSpec expr : expressions) {
if (matches(object, expr)) {
return true;
}
}
return false;
}

private static boolean matchesAnd(Object object, List<FilterSpec> expressions) {
for (FilterSpec expr : expressions) {
if (!matches(object, expr)) {
return false;
}
}
return true;
}

static class SortSpecComparator<T> implements Comparator<T> {

private List<SortSpec> sortSpecs;

public SortSpecComparator(List<SortSpec> sortSpecs) {
this.sortSpecs = sortSpecs;
}

@Override
@SuppressWarnings("unchecked")
public int compare(T o1, T o2) {
for (SortSpec orderSpec : sortSpecs) {
Comparable<Object> value1 = (Comparable<Object>) PropertyUtils.getProperty(o1,
orderSpec.getAttributePath());
Comparable<Object> value2 = (Comparable<Object>) PropertyUtils.getProperty(o2,
orderSpec.getAttributePath());

int d = compare(value1, value2);
if (orderSpec.getDirection() == Direction.DESC) {
d = -d;
}
if (d != 0)
return d;
}
return 0;
}

private int compare(Comparable<Object> value1, Comparable<Object> value2) {
if (value1 == null && value2 == null)
return 0;
if (value1 == null)
return -1;
if (value2 == null)
return 1;

return value1.compareTo(value2);
}
}
}
package io.katharsis.queryspec;

import java.util.ArrayList;
import java.util.Collection;
import java.util.Collections;
import java.util.Comparator;
import java.util.Iterator;
import java.util.List;

import io.katharsis.response.paging.PagedResultList;
import io.katharsis.utils.PropertyUtils;

/**
* Applies the given QuerySpec to the provided list in memory. Result available
* with getResult(). Use QuerySpec.apply to make use of this class.
*/
public class InMemoryEvaluator {

public <T> List<T> eval(Iterable<T> resources, QuerySpec querySpec) {
List<T> results = new ArrayList<>();

Iterator<T> iterator = resources.iterator();
while (iterator.hasNext()) {
results.add(iterator.next());
}

// filter
if (!querySpec.getFilters().isEmpty()) {
FilterSpec filterSpec = FilterSpec.and(querySpec.getFilters());
applyFilter(results, filterSpec);
}
long totalCount = results.size();

// sort
applySorting(results, querySpec.getSort());

// offset/limit
results = applyPaging(results, querySpec);

return new PagedResultList<>(results, totalCount);
}

private <T> void applySorting(List<T> results, List<SortSpec> sortSpec) {
if (!sortSpec.isEmpty()) {
Collections.sort(results, new SortSpecComparator<>(sortSpec));
}
}

private <T> List<T> applyPaging(List<T> results, QuerySpec querySpec) {
int offset = (int) Math.min(querySpec.getOffset(), Integer.MAX_VALUE);
int limit = (int) Math.min(Integer.MAX_VALUE, querySpec.getLimit() != null ? querySpec.getLimit() : Integer.MAX_VALUE);
limit = Math.min(results.size() - offset, limit);
if (offset > 0 || limit < results.size()) {
return results.subList(offset, offset + limit);
}
return results;
}

private <T> void applyFilter(List<T> results, FilterSpec filterSpec) {
if (filterSpec != null) {
Iterator<T> iterator = results.iterator();
while (iterator.hasNext()) {
T next = iterator.next();
if (!matches(next, filterSpec)) {
iterator.remove();
}
}
}
}

public static boolean matches(Object object, FilterSpec filterSpec) {
List<FilterSpec> expressions = filterSpec.getExpression();
if (expressions == null) {
return matchesPrimitiveOperator(object, filterSpec);
}
else if (filterSpec.getOperator() == FilterOperator.OR) {
return matchesOr(object, expressions);
}
else if (filterSpec.getOperator() == FilterOperator.AND) {
return matchesAnd(object, expressions);
}
else if (filterSpec.getOperator() == FilterOperator.NOT) {
return !matches(object, FilterSpec.and(expressions));
}
throw new UnsupportedOperationException("not implemented " + filterSpec);
}

private static boolean matchesPrimitiveOperator(Object object, FilterSpec filterSpec) {
Object value = PropertyUtils.getProperty(object, filterSpec.getAttributePath());
FilterOperator operator = filterSpec.getOperator();
Object filterValue = filterSpec.getValue();
if (value instanceof Collection) {
return matchesAny((Collection<?>) value, operator, filterValue);
}
else {
return operator.matches(value, filterValue);
}
}

private static boolean matchesAny(Collection<?> col, FilterOperator operator, Object filterValue) {
for (Object elem : col) {
boolean matches = operator.matches(elem, filterValue);
if (matches) {
return true;
}
}
return false;
}

private static boolean matchesOr(Object object, List<FilterSpec> expressions) {
for (FilterSpec expr : expressions) {
if (matches(object, expr)) {
return true;
}
}
return false;
}

private static boolean matchesAnd(Object object, List<FilterSpec> expressions) {
for (FilterSpec expr : expressions) {
if (!matches(object, expr)) {
return false;
}
}
return true;
}

static class SortSpecComparator<T> implements Comparator<T> {

private List<SortSpec> sortSpecs;

public SortSpecComparator(List<SortSpec> sortSpecs) {
this.sortSpecs = sortSpecs;
}

@Override
@SuppressWarnings("unchecked")
public int compare(T o1, T o2) {
for (SortSpec orderSpec : sortSpecs) {
Comparable<Object> value1 = (Comparable<Object>) PropertyUtils.getProperty(o1, orderSpec.getAttributePath());
Comparable<Object> value2 = (Comparable<Object>) PropertyUtils.getProperty(o2, orderSpec.getAttributePath());

int d = compare(value1, value2);
if (orderSpec.getDirection() == Direction.DESC) {
d = -d;
}
if (d != 0)
return d;
}
return 0;
}

private int compare(Comparable<Object> value1, Comparable<Object> value2) {
if (value1 == null && value2 == null)
return 0;
if (value1 == null)
return -1;
if (value2 == null)
return 1;

return value1.compareTo(value2);
}
}
}
16 changes: 14 additions & 2 deletions katharsis-core/src/main/java/io/katharsis/utils/PropertyUtils.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
import java.lang.reflect.Method;
import java.lang.reflect.Modifier;
import java.lang.reflect.Type;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.List;
Expand Down Expand Up @@ -92,9 +93,20 @@ public static Type getPropertyType(Class<?> beanClass, String field) {
public static Object getProperty(Object bean, List<String> propertyPath) {
Object current = bean;
for(String propertyName : propertyPath){
if(bean == null)
if(current == null)
return null;
current = getProperty(current, propertyName);
if(current instanceof Iterable){
// follow multi-valued property
List<Object> result = new ArrayList<>();
Iterable<?> iterable = (Iterable<?>)current;
for(Object currentElem : iterable){
result.add(getProperty(currentElem, propertyName));
}
current = result;
}else{
// follow single-valued property
current = getProperty(current, propertyName);
}
}
return current;
}
Expand Down
Loading

0 comments on commit 82aebc1

Please sign in to comment.