diff --git a/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/component/sql/optimizer/FilterSortedSkipOptimizer.java b/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/component/sql/optimizer/FilterSortedSkipOptimizer.java index 57ce6a2a26..836d38d0f6 100644 --- a/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/component/sql/optimizer/FilterSortedSkipOptimizer.java +++ b/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/component/sql/optimizer/FilterSortedSkipOptimizer.java @@ -24,20 +24,18 @@ import com.speedment.runtime.core.db.DbmsType; import static com.speedment.runtime.core.db.DbmsType.SkipLimitSupport.NONE; import static com.speedment.runtime.core.db.DbmsType.SkipLimitSupport.ONLY_AFTER_SORTED; -import com.speedment.runtime.core.db.FieldPredicateView; -import com.speedment.runtime.core.db.SqlPredicateFragment; import com.speedment.runtime.core.internal.stream.builder.action.reference.FilterAction; import com.speedment.runtime.core.internal.stream.builder.action.reference.LimitAction; import com.speedment.runtime.core.internal.stream.builder.action.reference.SkipAction; import com.speedment.runtime.core.internal.stream.builder.action.reference.SortedComparatorAction; -import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.isFilterActionWithFieldPredicate; +import com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil; +import com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.RenderResult; +import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.isContainingOnlyFieldPredicate; import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.isSortedActionWithFieldPredicate; import com.speedment.runtime.core.stream.Pipeline; import com.speedment.runtime.core.stream.action.Action; -import com.speedment.runtime.field.Field; import com.speedment.runtime.field.comparator.FieldComparator; import com.speedment.runtime.field.predicate.FieldPredicate; -import com.speedment.runtime.typemapper.TypeMapper; import java.util.ArrayList; import java.util.Arrays; import java.util.HashSet; @@ -46,7 +44,7 @@ import java.util.Set; import java.util.concurrent.atomic.AtomicInteger; import java.util.function.Consumer; -import static java.util.stream.Collectors.joining; +import java.util.function.Predicate; import static java.util.stream.Collectors.toList; /** @@ -144,36 +142,52 @@ public

P optimize( sql.append(info.getSqlSelect()); if (!filters.isEmpty()) { - final FieldPredicateView spv = info.getDbmsType().getFieldPredicateView(); - @SuppressWarnings("unchecked") - final List fragments = filters.stream() - .map(f -> f.getPredicate()) - .map(p -> (FieldPredicate) p) - .map(sp -> spv.transform(info.getSqlColumnNamer(), info.getSqlDatabaseTypeFunction(), sp)) + List> predicates = filters.stream() + .map(FilterAction::getPredicate) + .map(p -> (Predicate) p) +// .map(p -> (FieldPredicate) p) .collect(toList()); - // Todo: Make this in one sweep - String expression = fragments.stream() - .map(SqlPredicateFragment::getSql) - .collect(joining(" AND ")); - - sql.append(" WHERE ").append(expression); - - for (int i = 0; i < fragments.size(); i++) { - - @SuppressWarnings("unchecked") - final FieldPredicate p = (FieldPredicate) filters.get(i).getPredicate(); - final Field referenceFieldTrait = p.getField(); - - @SuppressWarnings("unchecked") - final TypeMapper tm = (TypeMapper) referenceFieldTrait.typeMapper(); - - fragments.get(i).objects() - .map(tm::toDatabaseType) - .forEach(values::add); + final RenderResult rr = StreamTerminatorUtil.renderSqlWhere( + info.getDbmsType(), + info.getSqlColumnNamer(), + info.getSqlDatabaseTypeFunction(), + predicates + ); - } +// final FieldPredicateView spv = info.getDbmsType().getFieldPredicateView(); +// +// // Todo: Allow composite predicates +// @SuppressWarnings("unchecked") +// final List fragments = filters.stream() +// .map(f -> f.getPredicate()) +// .map(p -> (FieldPredicate) p) +// .map(sp -> spv.transform(info.getSqlColumnNamer(), info.getSqlDatabaseTypeFunction(), sp)) +// .collect(toList()); +// +// // Todo: Make this in one sweep +// String expression = fragments.stream() +// .map(SqlPredicateFragment::getSql) +// .collect(joining(" AND ")); +// sql.append(" WHERE ").append(expression); +// +// for (int i = 0; i < fragments.size(); i++) { +// +// @SuppressWarnings("unchecked") +// final FieldPredicate p = (FieldPredicate) filters.get(i).getPredicate(); +// final Field referenceFieldTrait = p.getField(); +// +// @SuppressWarnings("unchecked") +// final TypeMapper tm = (TypeMapper) referenceFieldTrait.typeMapper(); +// +// fragments.get(i).objects() +// .map(tm::toDatabaseType) +// .forEachOrdered(values::add); +// +// } + sql.append(" WHERE ").append(rr.getSql()); + values.addAll(rr.getValues()); } @@ -237,7 +251,7 @@ private void traverse(Pipeline pipeline, final Action firstAction = pipeline.getFirst(); final List> path; - if (isFilterActionWithFieldPredicate(firstAction)) { + if (isFilterActionAndContainingOnlyFieldPredicate(firstAction)) { path = FILTER_SORTED_SKIP_PATH; } else { path = SORTED_FILTER_SKIP_PATH; @@ -305,59 +319,15 @@ private void traverse(Pipeline pipeline, } } -// private void traverseOld(Pipeline pipeline, -// final Consumer> filterConsumer, -// final Consumer> sortedConsumer, -// final Consumer> skipConsumer -// ) { -// -// State state = FILTER; -// -// for (Action action : pipeline) { -// -// if (state == FILTER) { -// if (isFilterActionWithFieldPredicate(action)) { -// @SuppressWarnings("unchecked") -// final FilterAction filterAction = (FilterAction) action; -// filterConsumer.accept(filterAction); -// } else { -// if (isSortedActionWithFieldPredicate(action)) { // Note: SortedAction will not work because an Entity is not Comparable -// state = SORTED; -// } else { -// if (action instanceof SkipAction) { -// state = SKIP; -// } else { -// return; -// } -// } -// } -// } -// -// if (state == SORTED) { -// if (isSortedActionWithFieldPredicate(action)) { -// @SuppressWarnings("unchecked") -// final SortedComparatorAction sortedAction = (SortedComparatorAction) action; -// sortedConsumer.accept(sortedAction); -// } else { -// if (action instanceof SkipAction) { -// state = SKIP; -// } else { -// return; -// } -// } -// } -// -// if (state == SKIP) { -// if (action instanceof SkipAction) { -// @SuppressWarnings("unchecked") -// final SkipAction skipAction = (SkipAction) action; -// skipConsumer.accept(skipAction); -// } else { -// return; -// } -// } -// } -// } + private boolean isFilterActionAndContainingOnlyFieldPredicate(Action action) { + if (action instanceof FilterAction) { + @SuppressWarnings("unchecked") + final FilterAction filterAction = (FilterAction) action; + return isContainingOnlyFieldPredicate(filterAction.getPredicate()); + } + return false; + } + private static class Consumers { private final Consumer> filterConsumer; @@ -407,7 +377,7 @@ private class FilterOperation implements Operation { @Override public boolean is(Action action) { - return isFilterActionWithFieldPredicate(action); + return isFilterActionAndContainingOnlyFieldPredicate(action); } @Override diff --git a/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtil.java b/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtil.java index e8f63bbb38..feabd926bf 100644 --- a/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtil.java +++ b/runtime-parent/runtime-core/src/main/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtil.java @@ -18,6 +18,7 @@ import com.speedment.runtime.core.component.sql.SqlStreamOptimizerInfo; import com.speedment.runtime.core.db.AsynchronousQueryResult; +import com.speedment.runtime.core.db.DbmsType; import com.speedment.runtime.core.db.FieldPredicateView; import com.speedment.runtime.core.db.SqlPredicateFragment; import com.speedment.runtime.core.internal.stream.builder.action.reference.FilterAction; @@ -28,12 +29,15 @@ import com.speedment.runtime.field.Field; import com.speedment.runtime.field.comparator.FieldComparator; import com.speedment.runtime.field.internal.predicate.AbstractCombinedPredicate; +import com.speedment.runtime.field.predicate.CombinedPredicate; import com.speedment.runtime.field.predicate.FieldPredicate; import com.speedment.runtime.typemapper.TypeMapper; import java.util.ArrayList; import java.util.List; import static java.util.Objects.requireNonNull; import java.util.Optional; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.function.Function; import java.util.function.Predicate; import static java.util.stream.Collectors.joining; import static java.util.stream.Collectors.toList; @@ -86,15 +90,15 @@ public static List> andPredicates(FilterAction action) { - if (action instanceof FilterAction) { - if (((FilterAction) action).getPredicate() instanceof FieldPredicate) { - return true; - } + public static boolean isContainingOnlyFieldPredicate(Predicate predicate) { + if (predicate instanceof FieldPredicate) { + return true; + } else if (predicate instanceof CombinedPredicate) { + return ((CombinedPredicate) predicate).stream().allMatch(StreamTerminatorUtil::isContainingOnlyFieldPredicate); } return false; } - + public static boolean isSortedActionWithFieldPredicate(Action action) { if (action instanceof SortedComparatorAction) { if (((SortedComparatorAction) action).getComparator() instanceof FieldComparator) { @@ -103,7 +107,7 @@ public static boolean isSortedActionWithFieldPredicate(Action action) { } return false; } - + public static void modifySource( final List> predicateBuilders, final SqlStreamOptimizerInfo info, @@ -140,8 +144,110 @@ public static void modifySource( query.setSql(sql); query.setValues(values); } - - - - private StreamTerminatorUtil() {throw new UnsupportedOperationException();} -} \ No newline at end of file + + public interface RenderResult { + + String getSql(); + + List getValues(); + + //Pipeline getPipeline(); + } + + private static final class RenderResultImpl implements RenderResult { + + private final String sql; + private final List values; + + public RenderResultImpl(String sql, List values /*, Pipeline pipeline*/) { + this.sql = sql; + this.values = values; + } + + @Override + public String getSql() { + return sql; + } + + @Override + public List getValues() { + return values; + } + + @Override + public String toString() { + return String.format("RenderResultImpl {sql=%s, values=%s}", sql, values); + } + } + + public static RenderResult renderSqlWhere( + final DbmsType dbmsType, + final Function, String> columnNamer, + final Function, Class> columnDbTypeFunction, + final List> predicates + ) { + final FieldPredicateView predicateView = dbmsType.getFieldPredicateView(); + final StringBuilder sql = new StringBuilder(); + final List values = new ArrayList<>(); + final AtomicInteger cnt = new AtomicInteger(); + predicates.forEach(predicate -> { + if (cnt.getAndIncrement() != 0) { + sql.append(" AND "); + } + renderSqlWhileHelper(predicateView, columnNamer, columnDbTypeFunction, sql, values, predicate); + }); + return new RenderResultImpl(sql.toString(), values); + } + + private static void renderSqlWhileHelper( + final FieldPredicateView spv, + final Function, String> columnNamer, + final Function, Class> columnDbTypeFunction, + final StringBuilder sql, + final List values, + final Predicate predicate + ) { + if (predicate instanceof FieldPredicate) { + final FieldPredicate fieldPredicate = (FieldPredicate) predicate; + final SqlPredicateFragment fragment = spv.transform(columnNamer, columnDbTypeFunction, fieldPredicate); + final Field referenceFieldTrait = fieldPredicate.getField(); + @SuppressWarnings("unchecked") + final TypeMapper tm = (TypeMapper) referenceFieldTrait.typeMapper(); + + sql.append(fragment.getSql()); + fragment.objects().map(tm::toDatabaseType).forEachOrdered(values::add); + } else if (predicate instanceof CombinedPredicate) { + final CombinedPredicate combinedPredicate = (CombinedPredicate) predicate; + final StringBuilder internalSql = new StringBuilder(); + final List internalValues = new ArrayList<>(); + final AtomicInteger cnt = new AtomicInteger(); + combinedPredicate.stream().forEachOrdered(internalPredicate -> { + if (cnt.getAndIncrement() != 0) { + internalSql.append(" ").append(combinedPredicate.getType().toString()).append(" "); + } + @SuppressWarnings("unchecked") + final Predicate castedInternalPredicate = (Predicate) internalPredicate; + renderSqlWhileHelper( + spv, + columnNamer, + columnDbTypeFunction, + internalSql, + internalValues, + castedInternalPredicate + ); + }); + if (combinedPredicate.isNegated()) { + sql.append("(NOT (").append(internalSql).append("))"); + } else { + sql.append("(").append(internalSql).append(")"); + } + values.addAll(internalValues); + } else { + throw new IllegalArgumentException("A predicate that is not instanceof FieldPredicate was given:" + predicate.toString()); + } + } + + private StreamTerminatorUtil() { + throw new UnsupportedOperationException(); + } +} diff --git a/runtime-parent/runtime-core/src/test/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtilTest.java b/runtime-parent/runtime-core/src/test/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtilTest.java new file mode 100644 index 0000000000..83faa74283 --- /dev/null +++ b/runtime-parent/runtime-core/src/test/java/com/speedment/runtime/core/internal/stream/builder/streamterminator/StreamTerminatorUtilTest.java @@ -0,0 +1,264 @@ +/** + * + * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.speedment.runtime.core.internal.stream.builder.streamterminator; + +import com.speedment.runtime.config.identifier.ColumnIdentifier; +import com.speedment.runtime.config.identifier.TableIdentifier; +import com.speedment.runtime.core.db.DbmsType; +import com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.RenderResult; +import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.isContainingOnlyFieldPredicate; +import static com.speedment.runtime.core.internal.stream.builder.streamterminator.StreamTerminatorUtil.renderSqlWhere; +import com.speedment.runtime.field.Field; +import com.speedment.runtime.field.IntField; +import com.speedment.runtime.field.internal.predicate.ints.IntBetweenPredicate; +import com.speedment.runtime.field.internal.predicate.ints.IntEqualPredicate; +import com.speedment.runtime.field.internal.predicate.ints.IntGreaterThanPredicate; +import com.speedment.runtime.field.predicate.FieldPredicate; +import com.speedment.runtime.field.predicate.Inclusion; +import com.speedment.runtime.test_support.MockDbmsType; +import com.speedment.runtime.typemapper.TypeMapper; +import java.util.Arrays; +import static java.util.Collections.singletonList; +import java.util.List; +import java.util.function.Consumer; +import java.util.function.Function; +import java.util.function.Predicate; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertFalse; +import static org.junit.Assert.assertTrue; +import static org.junit.Assert.fail; +import org.junit.Test; + +/** + * + * @author Per Minborg + */ +public class StreamTerminatorUtilTest { + + private static final DbmsType DBMS_TYPE = new MockDbmsType(); + private static final Function, String> COLUMN_NAMER = f -> f.identifier().getColumnName(); + private static final Function, Class> COLUMN_TYPE_FUNCTION = f -> Integer.class; + private static final FieldPredicate ID_GT_0 = new IntGreaterThanPredicate<>(Person.ID, 0); + private static final FieldPredicate ID_GT_1 = new IntGreaterThanPredicate<>(Person.ID, 1); + private static final FieldPredicate ID_LE_0 = ID_GT_0.negate(); + private static final FieldPredicate ID_LE_1 = ID_GT_1.negate(); + private static final FieldPredicate ID_EQ_0 = new IntEqualPredicate<>(Person.ID, 0); + private static final FieldPredicate ID_EQ_1 = new IntEqualPredicate<>(Person.ID, 1); + + private static final FieldPredicate AGE_GT_2 = new IntGreaterThanPredicate<>(Person.AGE, 2); + private static final FieldPredicate AGE_GT_3 = new IntGreaterThanPredicate<>(Person.AGE, 3); + private static final FieldPredicate AGE_LE_2 = AGE_GT_2.negate(); + private static final FieldPredicate AGE_LE_3 = AGE_GT_3.negate(); + private static final FieldPredicate AGE_EQ_2 = new IntEqualPredicate<>(Person.AGE, 2); + private static final FieldPredicate AGE_EQ_3 = new IntEqualPredicate<>(Person.AGE, 3); + + private static final FieldPredicate ID_BETWEEN_4_8 = new IntBetweenPredicate<>(Person.AGE, 4, 8, Inclusion.START_INCLUSIVE_END_EXCLUSIVE); + private static final FieldPredicate AGE_BETWEEN_10_12 = new IntBetweenPredicate<>(Person.AGE, 10, 12, Inclusion.START_INCLUSIVE_END_EXCLUSIVE); + + private static final Predicate ID_GT_0_AND_AGE_EQ_2 = ID_GT_0.and(AGE_EQ_2); + private static final Predicate ID_GT_0_OR_AGE_EQ_2 = ID_GT_0.or(AGE_EQ_2); + private static final Predicate COMPLEX = ID_GT_0.and(AGE_EQ_2).or(ID_GT_1.and(AGE_EQ_3)); + + @Test + public void testIsContainingOnlyFieldPredicateLambda() { + assertFalse(isContainingOnlyFieldPredicate(p -> true)); + } + + @Test + public void testIsContainingOnlyFieldPredicateSimple() { + assertTrue(isContainingOnlyFieldPredicate(ID_GT_0)); + } + + @Test + public void testIsContainingOnlyFieldPredicateAnd() { + assertTrue(isContainingOnlyFieldPredicate(ID_GT_0_AND_AGE_EQ_2)); + } + + @Test + public void testIsContainingOnlyFieldPredicateOr() { + assertTrue(isContainingOnlyFieldPredicate(ID_GT_0_AND_AGE_EQ_2)); + } + + @Test + public void testIsContainingOnlyFieldPredicateComplex() { + assertTrue(isContainingOnlyFieldPredicate(COMPLEX)); + } + + @Test + public void testIsContainingOnlyFieldPredicateComplexAndPolluted() { + assertFalse(isContainingOnlyFieldPredicate(COMPLEX.and(person -> true))); + } + + @Test + public void testBasicRenderSqlWhere() { + testRender(singletonList(ID_GT_0), rr -> { + System.out.println(rr); + assertEquals("(id > ?)", rr.getSql()); + assertEquals(singletonList(0), rr.getValues()); + }); + } + + @Test + public void testRenderSqlWhereNot() { + testRender(singletonList(ID_GT_0_AND_AGE_EQ_2.negate()), rr -> { + System.out.println(rr); + assertEquals("(NOT ((id > ?) AND (age = ?)))", rr.getSql()); + assertEquals(Arrays.asList(0, 2), rr.getValues()); + }); + } + + @Test + public void test2RenderSqlWhere() { + testRender(Arrays.asList(ID_GT_0, AGE_EQ_2), rr -> { + System.out.println(rr); + assertEquals("(id > ?) AND (age = ?)", rr.getSql()); + assertEquals(Arrays.asList(0, 2), rr.getValues()); + }); + } + + @Test + public void testAndComnbinedRenderSqlWhere() { + testRender(singletonList(ID_GT_0_AND_AGE_EQ_2), rr -> { + System.out.println(rr); + assertEquals("((id > ?) AND (age = ?))", rr.getSql()); + assertEquals(Arrays.asList(0, 2), rr.getValues()); + }); + } + + @Test + public void testOrComnbinedRenderSqlWhere() { + testRender(singletonList(ID_GT_0_OR_AGE_EQ_2), rr -> { + System.out.println(rr); + assertEquals("((id > ?) OR (age = ?))", rr.getSql()); + assertEquals(Arrays.asList(0, 2), rr.getValues()); + }); + } + + @Test + public void testComplexComnbinedRenderSqlWhere() { + testRender(singletonList(COMPLEX), rr -> { + System.out.println(rr); + assertEquals("(((id > ?) AND (age = ?)) OR ((id > ?) AND (age = ?)))", rr.getSql()); + assertEquals(Arrays.asList(0, 2, 1, 3), rr.getValues()); + }); + } + + @Test + public void testComplexPollutedComnbinedRenderSqlWhere() { + try { + testRender(singletonList(COMPLEX.and(p -> true)), rr -> { + }); + fail("method did not throw IllegalArgumentException"); + } catch (IllegalArgumentException e) { + // expected + } + } + + private void testRender(List> predicates, Consumer checker) { + final RenderResult rr = renderSqlWhere(DBMS_TYPE, COLUMN_NAMER, COLUMN_TYPE_FUNCTION, predicates); + checker.accept(rr); + } + + private static class Person { + + private int id; + private int age; + private static final IntField ID + = IntField.create( + Identifier.ID, + Person::getId, + Person::setId, + TypeMapper.primitive(), + true + ); + + private static final IntField AGE + = IntField.create( + Identifier.AGE, + Person::getId, + Person::setId, + TypeMapper.primitive(), + true + ); + + int getId() { + return id; + } + + Person setId(int id) { + this.id = id; + return this; + } + + int getAge() { + return age; + } + + Person setAge(int age) { + this.age = age; + return this; + } + + enum Identifier implements ColumnIdentifier { + ID("id"), + AGE("age"); + + private final String columnName; + private final TableIdentifier tableIdentifier; + + Identifier(String columnName) { + this.columnName = columnName; + this.tableIdentifier = TableIdentifier.of(getDbmsName(), + getSchemaName(), + getTableName()); + } + + @Override + public String getDbmsName() { + return "db0"; + } + + @Override + public String getSchemaName() { + return "SPEEDMENT"; + } + + @Override + public String getTableName() { + return "PERSON"; + } + + @Override + public String getColumnName() { + return this.columnName; + } + + @Override + public TableIdentifier asTableIdentifier() { + return this.tableIdentifier; + } + + } + + } + +} diff --git a/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicate.java b/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicate.java index 44f13b14cf..4ce29afbef 100644 --- a/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicate.java +++ b/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicate.java @@ -18,13 +18,16 @@ import com.speedment.runtime.field.predicate.CombinedPredicate; import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.List; import static java.util.Objects.requireNonNull; import java.util.function.Predicate; +import static java.util.stream.Collectors.joining; import java.util.stream.Stream; /** - * Aggregation of a number of {@link Predicate Predicates} of the same type + * Immutable aggregation of a number of {@link Predicate Predicates} of the same type * (e.g. AND or OR) that can be applied in combination. * * @param the entity type @@ -40,61 +43,13 @@ public abstract class AbstractCombinedPredicate extends AbstractPredicat private final Type type; private AbstractCombinedPredicate( - final Type type, - final Predicate first, - final Predicate second, + final Type type, + final List> predicates, final boolean negated ) { super(negated); this.type = requireNonNull(type); - this.predicates = new ArrayList<>(); - add(requireNonNull(first)); - add(requireNonNull(second)); - } - /** - * Creates a negated predicate of an original. - */ - private AbstractCombinedPredicate(AbstractCombinedPredicate original) { - super(!requireNonNull(original).isNegated()); - this.type = original.type; - this.predicates = original.predicates; - } - - /** - * Adds the provided Predicate to this CombinedBasePredicate. - * - * @param the Type of CombinedBasePredicate (AndCombinedBasePredicate or - * OrCombinedBasePredicate) - * @param predicate to add - * @return a reference to a CombinedPredicate after the method has been - * applied - */ - protected final > R add(Predicate predicate) { - requireNonNull(predicate); - if (getClass().equals(predicate.getClass())) { - @SuppressWarnings("unchecked") - final AbstractCombinedPredicate cbp = getClass().cast(predicate); - cbp.stream().forEachOrdered(predicates::add); - } else { - predicates.add(predicate); - } - - @SuppressWarnings("unchecked") - final R self = (R) this; - return self; - } - - /** - * Removes the provided Predicate from this CombinedBasePredicate. - * - * @param predicate to remove - * @return a reference to a CombinedPredicate after the method has been - * applied - */ - protected AbstractCombinedPredicate remove(Predicate predicate) { - requireNonNull(predicate); - predicates.remove(predicate); - return this; + this.predicates = new ArrayList<>(requireNonNull(predicates)); } @Override @@ -107,6 +62,10 @@ public int size() { return predicates.size(); } + protected List> getPredicates() { + return Collections.unmodifiableList(predicates); + } + @Override public Type getType() { return type; @@ -120,12 +79,11 @@ public Type getType() { public static class AndCombinedBasePredicate extends AbstractCombinedPredicate { - public AndCombinedBasePredicate(Predicate first, Predicate second) { - super(Type.AND, first, second, false); - } - - private AndCombinedBasePredicate(AndCombinedBasePredicate original) { - super(original); + public AndCombinedBasePredicate( + final List> predicates, + final boolean negated + ) { + super(Type.AND, requireNonNull(predicates), negated); } @Override @@ -137,30 +95,31 @@ protected boolean testWithoutNegation(ENTITY entity) { @Override public AndCombinedBasePredicate and(Predicate other) { requireNonNull(other); - return add(other); + final List> updatedPredicates = new ArrayList<>(getPredicates()); + updatedPredicates.add(other); + return new AndCombinedBasePredicate<>(updatedPredicates, isNegated()); } @Override public OrCombinedBasePredicate or(Predicate other) { requireNonNull(other); - return new OrCombinedBasePredicate<>(this, other); + return new OrCombinedBasePredicate<>(Arrays.asList(this, other), false); } @Override public AndCombinedBasePredicate negate() { - return new AndCombinedBasePredicate<>(this); + return new AndCombinedBasePredicate<>(getPredicates(), !isNegated()); } - + } public static class OrCombinedBasePredicate extends AbstractCombinedPredicate { - public OrCombinedBasePredicate(Predicate first, Predicate second) { - super(Type.OR, first, second, false); - } - - private OrCombinedBasePredicate(OrCombinedBasePredicate original) { - super(original); + public OrCombinedBasePredicate( + final List> predicates, + final boolean negated + ) { + super(Type.OR, requireNonNull(predicates), negated); } @Override @@ -172,30 +131,35 @@ protected boolean testWithoutNegation(ENTITY entity) { @Override public AndCombinedBasePredicate and(Predicate other) { requireNonNull(other); - return new AndCombinedBasePredicate<>(this, other); + return new AndCombinedBasePredicate<>(Arrays.asList(this, other), false); } @Override public OrCombinedBasePredicate or(Predicate other) { requireNonNull(other); - return add(other); + final List> updatedPredicates = new ArrayList<>(getPredicates()); + updatedPredicates.add(other); + return new OrCombinedBasePredicate<>(updatedPredicates, isNegated()); } @Override public OrCombinedBasePredicate negate() { - return new OrCombinedBasePredicate<>(this); + return new OrCombinedBasePredicate<>(getPredicates(), !isNegated()); } - + } @Override public String toString() { - return "{type=" + return "CombinedPredicate {type=" + type.name() + ", negated=" + isNegated() + ", predicates=" - + predicates.toString() + + predicates.stream() + .map(Object::toString) + .collect(joining(", ")) + "}"; } + } diff --git a/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/predicate/CombinedPredicate.java b/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/predicate/CombinedPredicate.java index 6b1ee9ba81..0938263e33 100644 --- a/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/predicate/CombinedPredicate.java +++ b/runtime-parent/runtime-field/src/main/java/com/speedment/runtime/field/predicate/CombinedPredicate.java @@ -17,6 +17,7 @@ package com.speedment.runtime.field.predicate; import com.speedment.runtime.field.internal.predicate.AbstractCombinedPredicate; +import java.util.Arrays; import java.util.function.Predicate; import java.util.stream.Stream; @@ -60,19 +61,48 @@ public enum Type { */ Type getType(); + /** + * Returns if this CombinedPredicate is negated. + * + * @return if this CombinedPredicate is negated + */ + boolean isNegated(); + @Override public CombinedPredicate and(Predicate other); @Override public CombinedPredicate or(Predicate other); - + @Override + public CombinedPredicate negate(); + + /** + * Creates and returns a new CombinedPredicate that is the logical AND + * combination of the given predicates. + * + * @param entity type + * @param first the first predicate used in the AND operation + * @param second the first predicate used in the AND operation + * @return a new CombinedPredicate that is the logical AND combination of + * the given predicates + */ static CombinedPredicate and(Predicate first, Predicate second) { - return new AbstractCombinedPredicate.AndCombinedBasePredicate<>(first, second); + return new AbstractCombinedPredicate.AndCombinedBasePredicate<>(Arrays.asList(first, second), false); } + /** + * Creates and returns a new CombinedPredicate that is the logical OR + * combination of the given predicates. + * + * @param entity type + * @param first the first predicate used in the OR operation + * @param second the first predicate used in the OR operation + * @return a new CombinedPredicate that is the logical OR combination of the + * given predicates + */ static CombinedPredicate or(Predicate first, Predicate second) { - return new AbstractCombinedPredicate.OrCombinedBasePredicate<>(first, second); + return new AbstractCombinedPredicate.OrCombinedBasePredicate<>(Arrays.asList(first, second), false); } } diff --git a/runtime-parent/runtime-field/src/test/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicateTest.java b/runtime-parent/runtime-field/src/test/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicateTest.java new file mode 100644 index 0000000000..46ef0893d5 --- /dev/null +++ b/runtime-parent/runtime-field/src/test/java/com/speedment/runtime/field/internal/predicate/AbstractCombinedPredicateTest.java @@ -0,0 +1,195 @@ +/** + * + * Copyright (c) 2006-2017, Speedment, Inc. All Rights Reserved. + * + * Licensed under the Apache License, Version 2.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://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, WITHOUT + * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the + * License for the specific language governing permissions and limitations under + * the License. + */ +/* + * To change this license header, choose License Headers in Project Properties. + * To change this template file, choose Tools | Templates + * and open the template in the editor. + */ +package com.speedment.runtime.field.internal.predicate; + +import com.speedment.runtime.field.predicate.CombinedPredicate; +import java.util.Arrays; +import java.util.List; +import java.util.Set; +import java.util.function.Predicate; +import static java.util.stream.Collectors.toSet; +import java.util.stream.Stream; +import static org.junit.Assert.*; +import org.junit.Test; + +/** + * + * @author Per Minborg + */ +public class AbstractCombinedPredicateTest { + + private static final Predicate MOD2 = new Predicate() { + @Override + public boolean test(Integer i) { + return i % 2 == 0; + } + + @Override + public String toString() { + return "MOD2"; + } + }; + private static final Predicate MOD4 = new Predicate() { + @Override + public boolean test(Integer i) { + return i % 4 == 0; + } + + @Override + public String toString() { + return "MOD4"; + } + }; + private static final Predicate MOD8 = new Predicate() { + @Override + public boolean test(Integer i) { + return i % 8 == 0; + } + + @Override + public String toString() { + return "MOD8"; + } + }; + + private static final Predicate MOD3 = new Predicate() { + @Override + public boolean test(Integer i) { + return i % 3 == 0; + } + + @Override + public String toString() { + return "MOD3"; + } + }; + + @Test + public void testStream() { + final Set> set = Stream.of(MOD2, MOD4, MOD8).collect(toSet()); + + CombinedPredicate p = CombinedPredicate.and(MOD2, MOD4); + p = p.and(MOD8); + assertTrue(p.stream().allMatch(set::contains)); + } + + @Test + public void testSize() { + CombinedPredicate p = CombinedPredicate.and(MOD2, MOD4); + p = p.and(MOD8); + final long actual = p.stream().count(); + assertEquals(3, actual); + } + + @Test + public void testGetPredicates() { + AbstractCombinedPredicate.AndCombinedBasePredicate p = new AbstractCombinedPredicate.AndCombinedBasePredicate<>(Arrays.asList(MOD2, MOD4), false); + p = p.and(MOD8); + final List> expected = Arrays.asList(MOD2, MOD4, MOD8); + final List> actual = p.getPredicates(); + assertEquals(expected, actual); + } + + @Test + public void testGetType() { + final CombinedPredicate or = CombinedPredicate.or(MOD2, MOD4); + assertEquals(CombinedPredicate.Type.OR, or.getType()); + final CombinedPredicate and = CombinedPredicate.and(MOD2, MOD4); + assertEquals(CombinedPredicate.Type.AND, and.getType()); + + final CombinedPredicate orComposed = CombinedPredicate.or(and, or); + assertEquals(CombinedPredicate.Type.OR, orComposed.getType()); + final CombinedPredicate andComposed = CombinedPredicate.and(and, or); + assertEquals(CombinedPredicate.Type.AND, andComposed.getType()); + + } + + @Test + public void testAnd() { + CombinedPredicate p = CombinedPredicate.and(MOD2, MOD4); + p = p.and(MOD8); + + for (int i = 0; i < 100; i++) { + boolean expected = MOD2.test(i) && MOD4.test(i) && MOD8.test(i); + boolean actual = p.test(i); + assertEquals(expected, actual); + } + + } + + @Test + public void testOr() { + CombinedPredicate p = CombinedPredicate.or(MOD2, MOD4); + p = p.or(MOD8); + + for (int i = 0; i < 100; i++) { + boolean expected = MOD2.test(i) || MOD4.test(i) || MOD8.test(i); + boolean actual = p.test(i); + assertEquals(expected, actual); + } + } + + @Test + public void testComposed() { + CombinedPredicate p = CombinedPredicate.and(MOD2, MOD4); + p = p.and(MOD8); + p = p.or(MOD3); + for (int i = 0; i < 100; i++) { + boolean expected = (MOD2.test(i) && MOD4.test(i) && MOD8.test(i)) || MOD3.test(i); + boolean actual = p.test(i); + assertEquals(expected, actual); + } + } + + @Test + public void testNegate() { + CombinedPredicate p = CombinedPredicate.and(MOD2, MOD4); + p = p.and(MOD8); + p = p.negate(); + for (int i = 0; i < 100; i++) { + boolean expected = !(MOD2.test(i) && MOD4.test(i) && MOD8.test(i)); + boolean actual = p.test(i); + assertEquals(expected, actual); + } + } + + @Test + public void testToString() { + CombinedPredicate p = CombinedPredicate.and(MOD2, MOD4); + p = p.and(MOD8).negate(); + final String toString = p.toString(); + System.out.println(toString); + assertTrue(toString.contains("negated=true")); + assertTrue(toString.contains("MOD2")); + assertTrue(toString.contains("MOD4")); + assertTrue(toString.contains("MOD8")); + assertTrue(toString.contains("type=AND")); + } + + @Test + public void testIsNegated() { + CombinedPredicate p = new AbstractCombinedPredicate.AndCombinedBasePredicate<>(Arrays.asList(MOD2, MOD4), false); + p = p.negate(); + assertTrue(p.isNegated()); + } + +}