diff --git a/gemma-core/src/main/java/ubic/gemma/core/util/ListUtils.java b/gemma-core/src/main/java/ubic/gemma/core/util/ListUtils.java index c574fb1ea4..2b24b8ed07 100644 --- a/gemma-core/src/main/java/ubic/gemma/core/util/ListUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/core/util/ListUtils.java @@ -1,9 +1,6 @@ package ubic.gemma.core.util; -import java.util.HashMap; -import java.util.List; -import java.util.Map; -import java.util.TreeMap; +import java.util.*; /** * Utilities and algorithms for {@link List}. @@ -44,4 +41,21 @@ private static void fillMap( Map element2position, List list } } } + + /** + * Pad a list to the next power of 2 with the given element. + */ + public static List padToNextPowerOfTwo( List list, Object elementForPadding ) { + int k = Integer.highestOneBit( list.size() ); + if ( list.size() == k ) { + return list; // already a power of 2 + } + k <<= 1; + List paddedList = new ArrayList<>( k ); + paddedList.addAll( list ); + for ( int j = list.size(); j < k; j++ ) { + paddedList.add( elementForPadding ); + } + return paddedList; + } } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractQueryFilteringVoEnabledDao.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractQueryFilteringVoEnabledDao.java index 2276f3be65..a965113728 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractQueryFilteringVoEnabledDao.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/AbstractQueryFilteringVoEnabledDao.java @@ -1,5 +1,6 @@ package ubic.gemma.persistence.service; +import org.apache.commons.collections4.IterableUtils; import org.apache.commons.lang3.NotImplementedException; import org.apache.commons.lang3.time.StopWatch; import org.hibernate.Query; @@ -9,9 +10,7 @@ import ubic.gemma.persistence.util.*; import javax.annotation.Nullable; -import java.util.Collection; -import java.util.List; -import java.util.Objects; +import java.util.*; import java.util.concurrent.TimeUnit; import java.util.stream.Collectors; @@ -166,7 +165,7 @@ public List loadIds( @Nullable Filters filters, @Nullable Sort sort ) { @Override public List loadIdsWithCache( @Nullable Filters filters, @Nullable Sort sort ) { - return doLoadIdsWithCache( filters, sort, true ); + return doLoadIdsWithCache( getByIdsFiltersIfPossible( filters ), sort, true ); } @Override @@ -176,7 +175,7 @@ public List load( @Nullable Filters filters, @Nullable Sort sort ) { @Override public Slice loadWithCache( @Nullable Filters filters, @Nullable Sort sort, int offset, int limit ) { - return doLoadWithCache( filters, sort, offset, limit, true ); + return doLoadWithCache( getByIdsFiltersIfPossible( filters ), sort, offset, limit, true ); } @Override @@ -186,7 +185,7 @@ public Slice load( @Nullable Filters filters, @Nullable Sort sort, int offset @Override public List loadWithCache( @Nullable Filters filters, @Nullable Sort sort ) { - return doLoadWithCache( filters, sort, true ); + return doLoadWithCache( getByIdsFiltersIfPossible( filters ), sort, true ); } @Override @@ -196,7 +195,7 @@ public Slice loadValueObjects( @Nullable Filters filters, @Nullable Sort sor @Override public Slice loadValueObjectsWithCache( @Nullable Filters filters, @Nullable Sort sort, int offset, int limit ) { - return doLoadValueObjectsWithCache( filters, sort, offset, limit, true ); + return doLoadValueObjectsWithCache( getByIdsFiltersIfPossible( filters ), sort, offset, limit, true ); } @Override @@ -206,7 +205,7 @@ public List loadValueObjects( @Nullable Filters filters, @Nullable Sort sort @Override public List loadValueObjectsWithCache( @Nullable Filters filters, @Nullable Sort sort ) { - return doLoadValueObjectsWithCache( filters, sort, true ); + return doLoadValueObjectsWithCache( getByIdsFiltersIfPossible( filters ), sort, true ); } @Override @@ -216,7 +215,30 @@ public long count( @Nullable Filters filters ) { @Override public long countWithCache( @Nullable Filters filters ) { - return doCountWithCache( filters, true ); + return doCountWithCache( getByIdsFiltersIfPossible( filters ), true ); + } + + /** + * Optimize a filter by replacing it with a filter over IDs if possible. + *

+ * This is taking advantage of the fact that we can cache the IDs of individual filters and intersect those results + * instead of running a complex database query. + * + * @param filters the filters to be optimized + * @return an equivalent filter, optimized + */ + private Filters getByIdsFiltersIfPossible( @Nullable Filters filters ) { + if ( filters != null ) { + Iterator> it = filters.iterator(); + if ( it.hasNext() ) { + Set ids = new HashSet<>( doLoadIdsWithCache( Filters.by( IterableUtils.toList( it.next() ) ), null, true ) ); + it.forEachRemaining( clause -> { + ids.retainAll( doLoadIdsWithCache( Filters.by( IterableUtils.toList( it.next() ) ), null, true ) ); + } ); + return Filters.by( getFilter( "id", Long.class, Filter.Operator.in, ids ) ); + } + } + return filters; } private List doLoadIdsWithCache( @Nullable Filters filters, @Nullable Sort sort, boolean cacheable ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java index be3793dd5d..8f304ad471 100755 --- a/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/service/expression/experiment/ExpressionExperimentServiceImpl.java @@ -564,7 +564,7 @@ public Filters getFiltersWithInferredAnnotations( Filters f, @Nullable Collectio // apply inference to terms // collect clauses mentioning terms final Map> termUrisBySubClause = new HashMap<>(); - for ( List clause : f ) { + for ( Iterable clause : f ) { Filters.FiltersClauseBuilder clauseBuilder = f2.and(); for ( Filter subClause : clause ) { if ( ArrayUtils.contains( PROPERTIES_USED_FOR_ANNOTATIONS, subClause.getOriginalProperty() ) ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/Filter.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/Filter.java index 78bc854f79..6e3af9952d 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/Filter.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/Filter.java @@ -38,6 +38,7 @@ import java.text.DateFormat; import java.text.ParseException; import java.util.Collection; +import java.util.Comparator; import java.util.Date; import java.util.stream.Collectors; @@ -45,13 +46,15 @@ /** * Holds the necessary information to filter an entity with a property, operator and right-hand side value. - * + *

+ * Clauses and sub-clauses are always sorted by objectAlias, propertyName, operator and requiredValue. A clause is + * sorted by its first element, if any. * @author tesarst * @author poirigui */ @Value -@EqualsAndHashCode(of = { "objectAlias", "operator", "requiredValue" }) -public class Filter implements PropertyMapping { +@EqualsAndHashCode(of = { "objectAlias", "propertyName", "operator", "requiredValue" }) +public class Filter implements PropertyMapping, Comparable { /** * This is only the date part of the ISO 8601 standard. @@ -63,6 +66,25 @@ public class Filter implements PropertyMapping { */ private static final ConfigurableConversionService conversionService = new GenericConversionService(); + private static final Comparator comparator = Comparator + .comparing( Filter::getObjectAlias, Comparator.nullsFirst( Comparator.naturalOrder() ) ) + .thenComparing( Filter::getPropertyName ) + .thenComparing( Filter::getOperator ) + .thenComparing( Filter::getComparableRequiredValue, Comparator.nullsLast( Comparator.naturalOrder() ) ); + + @Nullable + private Comparable getComparableRequiredValue() { + Object requiredValue = this.requiredValue; + if ( operator.requiredType != null && Collection.class.isAssignableFrom( operator.requiredType ) ) { + // unpack the first element of the collection + if ( requiredValue != null ) { + requiredValue = ( ( Collection ) requiredValue ).iterator().next(); + } + } + //noinspection unchecked + return ( Comparable ) requiredValue; + } + private static void addConverter( Class targetClass, Converter converter, Converter reverseConverter ) { conversionService.addConverter( String.class, targetClass, converter ); conversionService.addConverter( targetClass, String.class, reverseConverter ); @@ -253,6 +275,11 @@ private Filter( @Nullable String objectAlias, String propertyName, Class prop this.checkTypeCorrect(); } + @Override + public int compareTo( Filter filter ) { + return comparator.compare( this, filter ); + } + @Override public String toString() { return toString( false ); diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterCriteriaUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterCriteriaUtils.java index 1df9fb163a..e22a723dff 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterCriteriaUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterCriteriaUtils.java @@ -6,8 +6,10 @@ import javax.annotation.Nullable; import java.util.Collection; import java.util.List; -import java.util.Objects; +import java.util.stream.Collectors; +import static java.util.Objects.requireNonNull; +import static ubic.gemma.core.util.ListUtils.padToNextPowerOfTwo; import static ubic.gemma.persistence.util.PropertyMappingUtils.formProperty; /** @@ -26,8 +28,8 @@ public static Criterion formRestrictionClause( @Nullable Filters filters ) { Conjunction c = Restrictions.conjunction(); if ( filters == null || filters.isEmpty() ) return c; - for ( List clause : filters ) { - if ( clause == null || clause.isEmpty() ) + for ( Iterable clause : filters ) { + if ( clause == null || !clause.iterator().hasNext() ) continue; Disjunction d = Restrictions.disjunction(); for ( Filter subClause : clause ) { @@ -77,7 +79,7 @@ private static Criterion formRestrictionClause( Filter filter ) { return Restrictions.ne( property, filter.getRequiredValue() ); } case like: - return Restrictions.like( property, escapeLike( ( String ) Objects.requireNonNull( filter.getRequiredValue(), "Required value cannot be null for the like operator." ) ), MatchMode.START ); + return Restrictions.like( property, escapeLike( ( String ) requireNonNull( filter.getRequiredValue(), "Required value cannot be null for the like operator." ) ), MatchMode.START ); case lessThan: return Restrictions.lt( property, filter.getRequiredValue() ); case greaterThan: @@ -87,8 +89,12 @@ private static Criterion formRestrictionClause( Filter filter ) { case greaterOrEq: return Restrictions.ge( property, filter.getRequiredValue() ); case in: - return Restrictions.in( property, ( Collection ) Objects.requireNonNull( filter.getRequiredValue(), - "Required value cannot be null for a collection." ) ); + List item = requireNonNull( ( Collection ) filter.getRequiredValue(), "Required value cannot be null for a collection." ) + .stream().sorted().distinct().collect( Collectors.toList() ); + if ( item.isEmpty() ) { + throw new IllegalArgumentException( "The right hand size of a in operator cannot be empty." ); + } + return Restrictions.in( property, padToNextPowerOfTwo( item, item.get( item.size() - 1 ) ) ); default: throw new IllegalStateException( "Unexpected operator for filter: " + filter.getOperator() ); } diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterQueryUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterQueryUtils.java index 94084aada7..8c99fed1d7 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterQueryUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/FilterQueryUtils.java @@ -9,6 +9,7 @@ import java.util.stream.Collectors; import static java.util.Objects.requireNonNull; +import static ubic.gemma.core.util.ListUtils.padToNextPowerOfTwo; import static ubic.gemma.persistence.util.PropertyMappingUtils.formProperty; /** @@ -69,8 +70,8 @@ public static String formRestrictionClause( @Nullable Filters filters ) { return ""; int i = 0; StringBuilder conjunction = new StringBuilder(); - for ( List clause : filters ) { - if ( clause == null || clause.isEmpty() ) + for ( Iterable clause : filters ) { + if ( clause == null || !clause.iterator().hasNext() ) continue; StringBuilder disjunction = new StringBuilder(); boolean first = true; @@ -200,7 +201,7 @@ public static void addRestrictionParameters( Query query, @Nullable Filters filt private static void addRestrictionParameters( Query query, @Nullable Filters filters, int i ) { if ( filters == null ) return; - for ( List clause : filters ) { + for ( Iterable clause : filters ) { if ( clause == null ) continue; for ( Filter subClause : clause ) { @@ -212,8 +213,12 @@ private static void addRestrictionParameters( Query query, @Nullable Filters fil addRestrictionParameters( query, Filters.by( s.getFilter() ), i - 1 ); } else if ( subClause.getOperator().equals( Filter.Operator.in ) ) { // order is unimportant for this operation, so we can ensure that it is consistent and therefore cacheable - query.setParameterList( paramName, requireNonNull( ( Collection ) subClause.getRequiredValue(), "Required value cannot be null for the 'in' operator." ) - .stream().sorted().distinct().collect( Collectors.toList() ) ); + List item = requireNonNull( ( Collection ) subClause.getRequiredValue(), "Required value cannot be null for the 'in' operator." ) + .stream().sorted().distinct().collect( Collectors.toList() ); + if ( item.isEmpty() ) { + throw new IllegalArgumentException( "The right hand size of a in operator cannot be empty." ); + } + query.setParameterList( paramName, padToNextPowerOfTwo( item, item.get( item.size() - 1 ) ) ); } else if ( subClause.getOperator().equals( Filter.Operator.like ) ) { query.setParameter( paramName, escapeLike( ( String ) requireNonNull( subClause.getRequiredValue(), "Required value cannot be null for the 'like' operator." ) ) + "%" ); } else { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/Filters.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/Filters.java index f2822791a7..1f0e071450 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/Filters.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/Filters.java @@ -3,6 +3,7 @@ import lombok.EqualsAndHashCode; import javax.annotation.CheckReturnValue; +import javax.annotation.Nonnull; import javax.annotation.Nullable; import java.util.*; import java.util.stream.Collectors; @@ -12,18 +13,18 @@ * @author poirigui */ @EqualsAndHashCode(of = { "clauses" }) -public class Filters implements Iterable> { +public class Filters implements Iterable> { /** * Builder for a disjunctive sub-clause. */ public class FiltersClauseBuilder { - private final List subClauses; + private final SortedSet subClauses; private boolean built = false; private FiltersClauseBuilder() { - subClauses = new ArrayList<>(); + subClauses = new TreeSet<>(); } /** @@ -65,7 +66,7 @@ public FiltersClauseBuilder or( @Nullable String objectAlias, String propert } public Filters build() { - Filters.this.clauses.add( Collections.unmodifiableList( subClauses ) ); + Filters.this.clauses.add( Collections.unmodifiableSortedSet( subClauses ) ); built = true; return Filters.this; } @@ -95,6 +96,10 @@ public static Filters by( Filter... subClauses ) { return empty().and( subClauses ); } + public static Filters by( Collection subClauses ) { + return empty().and( subClauses ); + } + /** * Create a singleton {@link Filters} from an explicit clause. */ @@ -129,10 +134,40 @@ public static Filters by( Filters filters ) { return empty().and( filters ); } - private final List> clauses; + private final SortedSet> clauses; private Filters() { - this.clauses = new ArrayList<>(); + this.clauses = new TreeSet<>( getComparator() ); + } + + private Comparator> getComparator() { + return ( filters, otherFilter ) -> { + Iterator a = filters.iterator(); + Iterator b = otherFilter.iterator(); + while ( a.hasNext() && b.hasNext() ) { + int cmp = a.next().compareTo( b.next() ); + if ( cmp != 0 ) { + return cmp; + } + } + if ( a.hasNext() ) { + return 1; + } + if ( b.hasNext() ) { + return -1; + } + // clauses are identical, they will be collapsed + return 0; + }; + } + + @Nullable + private E firstOrNull( SortedSet set ) { + try { + return set.first(); + } catch ( NoSuchElementException e ) { + return null; + } } /** @@ -146,7 +181,12 @@ public FiltersClauseBuilder and() { * Add a clause of one or more {@link Filter} sub-clauses to the conjunction. */ public Filters and( Filter... filters ) { - clauses.add( Arrays.asList( filters ) ); + clauses.add( new TreeSet<>( Arrays.asList( filters ) ) ); + return this; + } + + public Filters and( Collection filters ) { + clauses.add( new TreeSet<>( filters ) ); return this; } @@ -184,8 +224,8 @@ public Filters and( @Nullable String objectAlias, String propertyName, Class * Add all the clauses of another filter to this. */ public Filters and( Filters filters ) { - for ( List clause : filters.clauses ) { - clauses.add( Collections.unmodifiableList( new ArrayList<>( clause ) ) ); + for ( SortedSet clause : filters.clauses ) { + clauses.add( Collections.unmodifiableSortedSet( new TreeSet<>( clause ) ) ); } return this; } @@ -197,15 +237,16 @@ public Filters and( Filters filters ) { */ public boolean isEmpty() { // hint: allMatch returns true if the stream is empty - return clauses.stream().allMatch( List::isEmpty ); + return clauses.stream().allMatch( SortedSet::isEmpty ); } /** * Obtain an iterator over the clauses contained in this conjunction. */ @Override - public Iterator> iterator() { - return clauses.iterator(); + @Nonnull + public Iterator> iterator() { + return clauses.stream().map( c -> ( Iterable ) c ).iterator(); } @Override diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/FiltersUtils.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/FiltersUtils.java index e5499c8cd8..14fb3aea63 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/FiltersUtils.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/FiltersUtils.java @@ -23,7 +23,7 @@ public static boolean containsAnyAlias( @Nullable Filters filters, @Nullable Sor } if ( filters == null ) return false; - for ( List clause : filters ) { + for ( Iterable clause : filters ) { if ( clause == null ) continue; for ( Filter subClause : clause ) { diff --git a/gemma-core/src/main/java/ubic/gemma/persistence/util/Subquery.java b/gemma-core/src/main/java/ubic/gemma/persistence/util/Subquery.java index 3b29ec059e..76363a5eaa 100644 --- a/gemma-core/src/main/java/ubic/gemma/persistence/util/Subquery.java +++ b/gemma-core/src/main/java/ubic/gemma/persistence/util/Subquery.java @@ -5,6 +5,7 @@ import org.springframework.util.Assert; import javax.annotation.Nullable; +import java.util.Comparator; import java.util.List; import java.util.Set; import java.util.stream.Collectors; @@ -25,7 +26,7 @@ * @see Filter#by(String, String, Class, Filter.Operator, Subquery, String) */ @Value -public class Subquery { +public class Subquery implements Comparable { @Value public static class Alias { @@ -84,6 +85,11 @@ public Subquery( String entityName, String propertyName, List aliases, Fi this.filter = filter; } + @Override + public int compareTo( Subquery subquery ) { + return filter.compareTo( subquery.filter ); + } + public String toString() { String rootAlias = getRootAlias(); String jointures = aliases.stream() diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/util/FilterQueryUtilsTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/util/FilterQueryUtilsTest.java index 895a98a46e..baac05f1de 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/util/FilterQueryUtilsTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/util/FilterQueryUtilsTest.java @@ -33,15 +33,15 @@ public void testComplexClause() { .or( Filter.parse( "ee", "id", Long.class, Filter.Operator.in, Arrays.asList( "5", "6", "7", "8" ) ) ) .build(); assertThat( formRestrictionClause( filters ) ) - .isEqualTo( " and (ee.shortName like :ee_shortName1) and (ee.id in (:ee_id2)) and (ad.taxonId = :ad_taxonId3) and (ee.id in (:ee_id4) or ee.id in (:ee_id5))" ); + .isEqualTo( " and (ad.taxonId = :ad_taxonId1) and (ee.id in (:ee_id2)) and (ee.id in (:ee_id3) or ee.id in (:ee_id4)) and (ee.shortName like :ee_shortName5)" ); Query mockedQuery = mock( Query.class ); addRestrictionParameters( mockedQuery, filters ); - verify( mockedQuery ).setParameter( "ee_shortName1", "GSE%" ); + verify( mockedQuery ).setParameter( "ad_taxonId1", 9606L ); verify( mockedQuery ).setParameterList( "ee_id2", Arrays.asList( 1L, 2L, 3L, 4L ) ); - verify( mockedQuery ).setParameter( "ad_taxonId3", 9606L ); - verify( mockedQuery ).setParameterList( "ee_id4", Arrays.asList( 1L, 2L, 3L, 4L ) ); - verify( mockedQuery ).setParameterList( "ee_id5", Arrays.asList( 5L, 6L, 7L, 8L ) ); + verify( mockedQuery ).setParameterList( "ee_id3", Arrays.asList( 1L, 2L, 3L, 4L ) ); + verify( mockedQuery ).setParameterList( "ee_id4", Arrays.asList( 5L, 6L, 7L, 8L ) ); + verify( mockedQuery ).setParameter( "ee_shortName5", "GSE%" ); } @Test diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/util/FiltersTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/util/FiltersTest.java index 4c115a9dc7..3203c39c3b 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/util/FiltersTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/util/FiltersTest.java @@ -3,6 +3,7 @@ import org.apache.commons.collections4.IterableUtils; import org.junit.Test; +import java.util.ArrayList; import java.util.List; import static org.assertj.core.api.Assertions.assertThat; @@ -22,13 +23,16 @@ public void test() { .or( "ee", "shortName", String.class, Filter.Operator.like, "%brain%" ) .or( "ee", "description", String.class, Filter.Operator.like, "%tumor%" ) .build(); - List> clauses = IterableUtils.toList( filters ); - assertThat( clauses ).hasSize( 4 ); - assertThat( clauses.get( 0 ) ).extracting( "requiredValue" ).containsExactly( 3L ); - assertThat( clauses.get( 1 ) ).extracting( "requiredValue" ).containsExactly( 1L, 2L ); - assertThat( clauses.get( 2 ) ).isEmpty(); - assertThat( clauses.get( 3 ) ).extracting( "requiredValue" ).containsExactly( "%brain%", "%tumor%" ); - assertThat( filters ).hasToString( "ee.id = 3 and ee.id = 1 or ee.id = 2 and ee.shortName like %brain% or ee.description like %tumor%" ); + assertThat( filters ) + .hasSize( 4 ) + .hasToString( "ee.description like %tumor% or ee.shortName like %brain% and ee.id = 1 or ee.id = 2 and ee.id = 3" ) + .flatExtracting( it -> { + List f = new ArrayList<>(); + it.forEach( f::add ); + return f; + } ) + .extracting( Filter::getRequiredValue ) + .containsExactly( "%tumor%", "%brain%", 1L, 2L, 3L ); } @Test diff --git a/gemma-core/src/test/java/ubic/gemma/persistence/util/ListUtilsTest.java b/gemma-core/src/test/java/ubic/gemma/persistence/util/ListUtilsTest.java index 0745b433b4..44e2d5cc03 100644 --- a/gemma-core/src/test/java/ubic/gemma/persistence/util/ListUtilsTest.java +++ b/gemma-core/src/test/java/ubic/gemma/persistence/util/ListUtilsTest.java @@ -4,6 +4,7 @@ import ubic.gemma.core.util.ListUtils; import java.util.Arrays; +import java.util.Collections; import java.util.Map; import static org.assertj.core.api.Assertions.assertThat; @@ -33,4 +34,12 @@ public void testIndexOfCaseInsensitiveStringElements() { assertThat( str2position.get( "A" ) ).isEqualTo( 0 ); assertThat( str2position.get( "baBa" ) ).isEqualTo( 2 ); } + + @Test + public void testPadToNextPowerOfTwo() { + assertThat( ListUtils.padToNextPowerOfTwo( Collections.emptyList(), null ) ).hasSize( 0 ); + assertThat( ListUtils.padToNextPowerOfTwo( Arrays.asList( 1L, 2L, 3L ), null ) ).hasSize( 4 ); + assertThat( ListUtils.padToNextPowerOfTwo( Arrays.asList( 1L, 2L, 3L, 4L ), null ) ).hasSize( 4 ); + assertThat( ListUtils.padToNextPowerOfTwo( Arrays.asList( 1L, 2L, 3L, 4L, 5L ), null ) ).hasSize( 8 ); + } } \ No newline at end of file diff --git a/gemma-rest/src/test/java/ubic/gemma/rest/AnnotationsWebServiceTest.java b/gemma-rest/src/test/java/ubic/gemma/rest/AnnotationsWebServiceTest.java index 767bf5c08c..9c8b70fec1 100644 --- a/gemma-rest/src/test/java/ubic/gemma/rest/AnnotationsWebServiceTest.java +++ b/gemma-rest/src/test/java/ubic/gemma/rest/AnnotationsWebServiceTest.java @@ -147,7 +147,7 @@ public void testSearchTaxonDatasets() throws SearchException { LimitArg.valueOf( "20" ), SortArg.valueOf( "+id" ) ); assertThat( payload ) - .hasFieldOrPropertyWithValue( "filter", "commonName = human or scientificName = human and id in (1)" ) + .hasFieldOrPropertyWithValue( "filter", "id in (1) and commonName = human or scientificName = human" ) .hasFieldOrPropertyWithValue( "sort", new SortValueObject( Sort.by( "ee", "id", Sort.Direction.ASC, "id" ) ) ) .hasFieldOrPropertyWithValue( "offset", 0 ) .hasFieldOrPropertyWithValue( "limit", 20 ) diff --git a/gemma-rest/src/test/java/ubic/gemma/rest/DatasetsRestTest.java b/gemma-rest/src/test/java/ubic/gemma/rest/DatasetsRestTest.java index 81b9754209..183b8f5f68 100644 --- a/gemma-rest/src/test/java/ubic/gemma/rest/DatasetsRestTest.java +++ b/gemma-rest/src/test/java/ubic/gemma/rest/DatasetsRestTest.java @@ -135,7 +135,7 @@ public void testAllFilterById() { public void testAllFilterByIdIn() { FilterArg filterArg = FilterArg.valueOf( "id in (" + ees.get( 0 ).getId() + ")" ); assertThat( datasetArgService.getFilters( filterArg ) ) - .extracting( of -> of.get( 0 ) ) + .extracting( of -> of.iterator().next() ) .first() .hasFieldOrPropertyWithValue( "objectAlias", ExpressionExperimentDao.OBJECT_ALIAS ) .hasFieldOrPropertyWithValue( "propertyName", "id" ) @@ -159,7 +159,7 @@ public void testAllFilterByIdIn() { public void testAllFilterByShortName() { FilterArg filterArg = FilterArg.valueOf( "shortName = " + ees.get( 0 ).getShortName() ); assertThat( datasetArgService.getFilters( filterArg ) ) - .extracting( of -> of.get( 0 ) ) + .extracting( of -> of.iterator().next() ) .first() .hasFieldOrPropertyWithValue( "objectAlias", ExpressionExperimentDao.OBJECT_ALIAS ) .hasFieldOrPropertyWithValue( "propertyName", "shortName" ) @@ -183,7 +183,7 @@ public void testAllFilterByShortName() { public void testAllFilterByShortNameIn() { FilterArg filterArg = FilterArg.valueOf( "shortName in (" + ees.get( 0 ).getShortName() + ")" ); assertThat( datasetArgService.getFilters( filterArg ) ) - .extracting( of -> of.get( 0 ) ) + .extracting( of -> of.iterator().next() ) .first() .hasFieldOrPropertyWithValue( "objectAlias", ExpressionExperimentDao.OBJECT_ALIAS ) .hasFieldOrPropertyWithValue( "propertyName", "shortName" ) diff --git a/gemma-rest/src/test/java/ubic/gemma/rest/util/args/FilterArgTest.java b/gemma-rest/src/test/java/ubic/gemma/rest/util/args/FilterArgTest.java index c9fd2ac99d..a6b048c43c 100644 --- a/gemma-rest/src/test/java/ubic/gemma/rest/util/args/FilterArgTest.java +++ b/gemma-rest/src/test/java/ubic/gemma/rest/util/args/FilterArgTest.java @@ -57,7 +57,7 @@ public void testSimpleEquality() { setUpMockVoService(); Filters filters = FilterArg.valueOf( "a = b" ).getFilters( mockVoService ); assertThat( filters ) - .extracting( of -> of.get( 0 ) ) + .extracting( of -> of.iterator().next() ) .first() .hasFieldOrPropertyWithValue( "propertyName", "a" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) @@ -97,13 +97,13 @@ public void testConjunction() { Filters filters = FilterArg.valueOf( "a = b and c = d" ).getFilters( mockVoService ); assertThat( filters ).hasSize( 2 ); assertThat( filters ) - .extracting( of -> of.get( 0 ) ) + .extracting( of -> of.iterator().next() ) .first() .hasFieldOrPropertyWithValue( "propertyName", "a" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) .hasFieldOrPropertyWithValue( "requiredValue", "b" ); assertThat( filters ) - .extracting( of -> of.get( 0 ) ) + .extracting( of -> of.iterator().next() ) .last() .hasFieldOrPropertyWithValue( "propertyName", "c" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) @@ -118,27 +118,27 @@ public void testDisjunction() { assertThat( filters.iterator().next() ) .hasSize( 2 ); - assertThat( filters.iterator().next().get( 0 ) ) + assertThat( filters.iterator().next().iterator().next() ) .hasFieldOrPropertyWithValue( "propertyName", "a" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) .hasFieldOrPropertyWithValue( "requiredValue", "b" ); - assertThat( filters.iterator().next().get( 1 ) ) - .hasFieldOrPropertyWithValue( "propertyName", "c" ) - .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) - .hasFieldOrPropertyWithValue( "requiredValue", "d" ); + // assertThat( filters.iterator().next().get( 1 ) ) + // .hasFieldOrPropertyWithValue( "propertyName", "c" ) + // .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) + // .hasFieldOrPropertyWithValue( "requiredValue", "d" ); FilterArg.valueOf( "a = b or c = d" ).getFilters( mockVoService ); - assertThat( filters.iterator().next().get( 0 ) ) + assertThat( filters.iterator().next().iterator().next() ) .hasFieldOrPropertyWithValue( "propertyName", "a" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) .hasFieldOrPropertyWithValue( "requiredValue", "b" ); - assertThat( filters.iterator().next().get( 1 ) ) - .hasFieldOrPropertyWithValue( "propertyName", "c" ) - .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) - .hasFieldOrPropertyWithValue( "requiredValue", "d" ); + // assertThat( filters.iterator().next().get( 1 ) ) + // .hasFieldOrPropertyWithValue( "propertyName", "c" ) + // .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) + // .hasFieldOrPropertyWithValue( "requiredValue", "d" ); } @Test @@ -148,32 +148,32 @@ public void testConjunctionOfDisjunctions() { assertThat( filters ).hasSize( 2 ); - Iterator> iterator = filters.iterator(); - List of; + Iterator> iterator = filters.iterator(); + Iterable of; of = iterator.next(); assertThat( of ) .hasSize( 2 ); - assertThat( of.get( 0 ) ) + assertThat( of.iterator().next() ) .hasFieldOrPropertyWithValue( "propertyName", "a" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) .hasFieldOrPropertyWithValue( "requiredValue", "b" ); - assertThat( of.get( 1 ) ) - .hasFieldOrPropertyWithValue( "propertyName", "g" ) - .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) - .hasFieldOrPropertyWithValue( "requiredValue", "h" ); + // assertThat( of.get( 1 ) ) + // .hasFieldOrPropertyWithValue( "propertyName", "g" ) + // .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) + // .hasFieldOrPropertyWithValue( "requiredValue", "h" ); of = iterator.next(); - assertThat( of.get( 0 ) ) + assertThat( of.iterator().next() ) .hasFieldOrPropertyWithValue( "propertyName", "c" ) .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) .hasFieldOrPropertyWithValue( "requiredValue", "d" ); - assertThat( of.get( 1 ) ) - .hasFieldOrPropertyWithValue( "propertyName", "e" ) - .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) - .hasFieldOrPropertyWithValue( "requiredValue", "f" ); + // assertThat( of.get( 1 ) ) + // .hasFieldOrPropertyWithValue( "propertyName", "e" ) + // .hasFieldOrPropertyWithValue( "operator", Filter.Operator.eq ) + // .hasFieldOrPropertyWithValue( "requiredValue", "f" ); } @Test @@ -186,7 +186,7 @@ public void testParseDate() { FilterArg fa = FilterArg.valueOf( "lastUpdated >= 2022-01-01" ); Filters f = fa.getFilters( mockVoService ); assertThat( f ).isNotNull(); - Filter subClause = f.iterator().next().get( 0 ); + Filter subClause = f.iterator().next().iterator().next(); assertThat( subClause ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "lastUpdated" ); @@ -198,7 +198,7 @@ public void testParseDate() { FilterArg fa2 = FilterArg.valueOf( subClause.toString() ); Filters f2 = fa2.getFilters( mockVoService ); assertThat( f2 ).isNotNull(); - Filter subClause2 = f2.iterator().next().get( 0 ); + Filter subClause2 = f2.iterator().next().iterator().next(); assertThat( subClause2 ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "alias.lastUpdated" ); @@ -225,7 +225,7 @@ private void checkValidString( String expected, String s ) { FilterArg fa = FilterArg.valueOf( "foo >= " + s ); Filters f = fa.getFilters( mockVoService ); assertThat( f ).isNotNull(); - Filter subClause = f.iterator().next().get( 0 ); + Filter subClause = f.iterator().next().iterator().next(); assertThat( subClause ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "foo" ) @@ -238,7 +238,7 @@ public void testParseStringUnescapeQuote() { FilterArg fa = FilterArg.valueOf( "foo >= \"\\\"\"" ); Filters f = fa.getFilters( mockVoService ); assertThat( f ).isNotNull(); - Filter subClause = f.iterator().next().get( 0 ); + Filter subClause = f.iterator().next().iterator().next(); assertThat( subClause ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "foo" ) @@ -290,7 +290,7 @@ public void testBase64EncodedFilter() { setUpMockVoService(); FilterArg arg = FilterArg.valueOf( "H4sIAAAAAAAAA8tMUbBVMAQA2dNQugYAAAA=" ); Filters f = arg.getFilters( mockVoService ); - Filter subClause = f.iterator().next().get( 0 ); + Filter subClause = f.iterator().next().iterator().next(); assertThat( subClause ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "id" ) @@ -302,7 +302,7 @@ public void testPropertyStartingWithGzipMagicNumber() { setUpMockVoService(); FilterArg arg = FilterArg.valueOf( "H4s = 1" ); Filters f = arg.getFilters( mockVoService ); - Filter subClause = f.iterator().next().get( 0 ); + Filter subClause = f.iterator().next().iterator().next(); assertThat( subClause ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "H4s" ) @@ -312,7 +312,7 @@ public void testPropertyStartingWithGzipMagicNumber() { private void checkValidCollection( Collection expected, String input ) { FilterArg fa = FilterArg.valueOf( "foo in " + input ); Filters f = fa.getFilters( mockVoService ); - Filter subClause = f.iterator().next().get( 0 ); + Filter subClause = f.iterator().next().iterator().next(); assertThat( subClause ) .hasFieldOrPropertyWithValue( "objectAlias", "alias" ) .hasFieldOrPropertyWithValue( "propertyName", "foo" )