Author: desruisseaux
Date: Thu May 10 19:08:39 2018
New Revision: 1831365
URL: http://svn.apache.org/viewvc?rev=1831365&view=rev
Log:
Implement JoinFeatureSet.features(boolean) on top of Spliterator instead than
Iterator,
and use an identifier created by FeatureOperations.compound(…) instead than
computing
the string concatenation unconditionally for every features.
Added:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java
- copied, changed from r1831270,
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java
- copied, changed from r1831270,
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java
- copied, changed from r1831270,
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
Modified:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
sis/branches/JDK8/storage/sis-storage/src/test/java/org/apache/sis/internal/storage/JoinFeatureSetTest.java
Copied:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java
(from r1831270,
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java)
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java?p2=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java&p1=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java&r1=1831270&r2=1831365&rev=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractBinaryOperator.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -17,145 +17,97 @@
package org.apache.sis.filter;
import java.io.Serializable;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.MatchAction;
+import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
/**
- * Base class for filters that compare exactly two values against each other.
- * The nature of the comparison is dependent on the subclass.
+ * Base class for filters performing operations on two values.
+ * The nature of the operation is dependent on the subclass.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
-abstract class AbstractComparisonOperator implements BinaryComparisonOperator,
Serializable {
+abstract class AbstractBinaryOperator implements Filter, Serializable {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = -1401452229232869720L;
+ private static final long serialVersionUID = -5079157703540568112L;
/**
- * The first of the two expressions to be compared by this operator.
+ * The first of the two expressions to be used by this operator.
*
* @see #getExpression1()
*/
protected final Expression expression1;
/**
- * The second of the two expressions to be compared by this operator.
+ * The second of the two expressions to be used by this operator.
*
* @see #getExpression2()
*/
protected final Expression expression2;
/**
- * Whether comparisons are case sensitive.
- *
- * @see #isMatchingCase()
- */
- protected final boolean matchCase;
-
- /**
- * Specifies how the comparison predicate shall be evaluated for a
collection of values.
- *
- * @see #getMatchAction()
- */
- protected final MatchAction matchAction;
-
- /**
- * Creates a new binary comparison operator.
+ * Creates a new binary operator.
* It is caller responsibility to ensure that no argument is null.
*/
- AbstractComparisonOperator(final Expression expression1, final Expression
expression2,
- final boolean matchCase, final MatchAction
matchAction)
- {
+ AbstractBinaryOperator(final Expression expression1, final Expression
expression2) {
this.expression1 = expression1;
this.expression2 = expression2;
- this.matchCase = matchCase;
- this.matchAction = matchAction;
}
/**
- * Returns the mathematical symbol for this comparison operator.
- * The symbol should be one of the following: {@literal < > ≤ ≥ = ≠}.
+ * Returns the mathematical symbol for this binary operator.
+ * For comparison operators, the symbol should be one of the following:
+ * {@literal < > ≤ ≥ = ≠}.
*/
protected abstract char symbol();
/**
- * Returns the first of the two expressions to be compared by this
operator.
+ * Returns the first of the two expressions to be used by this operator.
*/
- @Override
public final Expression getExpression1() {
return expression1;
}
/**
- * Returns the second of the two expressions to be compared by this
operator.
+ * Returns the second of the two expressions to be used by this operator.
*/
- @Override
public final Expression getExpression2() {
return expression2;
}
/**
- * Specifies whether comparisons are case sensitive.
- *
- * @return {@code true} if the comparisons are case sensitive, otherwise
{@code false}.
+ * Returns a hash code value for this operator.
*/
@Override
- public final boolean isMatchingCase() {
- return matchCase;
- }
-
- /**
- * Specifies how the comparison predicate shall be evaluated for a
collection of values.
- * Values can be {@link MatchAction#ALL ALL} if all values in the
collection shall satisfy the predicate,
- * {@link MatchAction#ANY ANY} if any of the value in the collection can
satisfy the predicate, or
- * {@link MatchAction#ONE ONE} if only one of the values in the collection
shall satisfy the predicate.
- *
- * @return how the comparison predicate shall be evaluated for a
collection of values.
- */
- @Override
- public final MatchAction getMatchAction() {
- return matchAction;
- }
-
- /**
- * Returns a hash code value for this comparison operator.
- */
- @Override
- public final int hashCode() {
- int hash = (31 * expression1.hashCode() + expression2.hashCode()) * 37
+ matchAction.hashCode();
- if (matchCase) hash = ~hash;
- return hash ^ symbol(); // Use the symbol as a way to
differentiate the subclasses.
+ public int hashCode() {
+ // We use the symbol as a way to differentiate the subclasses.
+ return (31 * expression1.hashCode() + expression2.hashCode()) ^
symbol();
}
/**
* Compares this operator with the given object for equality.
*/
@Override
- public final boolean equals(final Object obj) {
- if (obj == this) {
- return true;
- }
+ public boolean equals(final Object obj) {
if (obj != null && obj.getClass() == getClass()) {
- final AbstractComparisonOperator other =
(AbstractComparisonOperator) obj;
- return matchCase == other.matchCase &&
- expression1.equals(other.expression1) &&
- expression2.equals(other.expression2) &&
- matchAction.equals(other.matchAction);
+ final AbstractBinaryOperator other = (AbstractBinaryOperator) obj;
+ return expression1.equals(other.expression1) &&
+ expression2.equals(other.expression2);
}
return false;
}
/**
- * Returns a string representation of this comparison operator.
+ * Returns a string representation of this operator.
*/
@Override
- public final String toString() {
+ public String toString() {
return new StringBuilder(30).append(expression1).append('
').append(symbol()).append(' ')
.append(expression2).toString();
}
Modified:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java?rev=1831365&r1=1831364&r2=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -27,29 +27,16 @@ import org.opengis.filter.expression.Exp
* The nature of the comparison is dependent on the subclass.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
-abstract class AbstractComparisonOperator implements BinaryComparisonOperator,
Serializable {
+abstract class AbstractComparisonOperator extends AbstractBinaryOperator
implements BinaryComparisonOperator, Serializable {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = -1401452229232869720L;
-
- /**
- * The first of the two expressions to be compared by this operator.
- *
- * @see #getExpression1()
- */
- protected final Expression expression1;
-
- /**
- * The second of the two expressions to be compared by this operator.
- *
- * @see #getExpression2()
- */
- protected final Expression expression2;
+ private static final long serialVersionUID = -4709016194087609721L;
/**
* Whether comparisons are case sensitive.
@@ -72,35 +59,12 @@ abstract class AbstractComparisonOperato
AbstractComparisonOperator(final Expression expression1, final Expression
expression2,
final boolean matchCase, final MatchAction
matchAction)
{
- this.expression1 = expression1;
- this.expression2 = expression2;
+ super(expression1, expression2);
this.matchCase = matchCase;
this.matchAction = matchAction;
}
/**
- * Returns the mathematical symbol for this comparison operator.
- * The symbol should be one of the following: {@literal < > ≤ ≥ = ≠}.
- */
- protected abstract char symbol();
-
- /**
- * Returns the first of the two expressions to be compared by this
operator.
- */
- @Override
- public final Expression getExpression1() {
- return expression1;
- }
-
- /**
- * Returns the second of the two expressions to be compared by this
operator.
- */
- @Override
- public final Expression getExpression2() {
- return expression2;
- }
-
- /**
* Specifies whether comparisons are case sensitive.
*
* @return {@code true} if the comparisons are case sensitive, otherwise
{@code false}.
@@ -128,9 +92,9 @@ abstract class AbstractComparisonOperato
*/
@Override
public final int hashCode() {
- int hash = (31 * expression1.hashCode() + expression2.hashCode()) * 37
+ matchAction.hashCode();
+ int hash = super.hashCode() * 37 + matchAction.hashCode();
if (matchCase) hash = ~hash;
- return hash ^ symbol(); // Use the symbol as a way to
differentiate the subclasses.
+ return hash;
}
/**
@@ -141,22 +105,10 @@ abstract class AbstractComparisonOperato
if (obj == this) {
return true;
}
- if (obj != null && obj.getClass() == getClass()) {
+ if (super.equals(obj)) {
final AbstractComparisonOperator other =
(AbstractComparisonOperator) obj;
- return matchCase == other.matchCase &&
- expression1.equals(other.expression1) &&
- expression2.equals(other.expression2) &&
- matchAction.equals(other.matchAction);
+ return matchCase == other.matchCase &&
matchAction.equals(other.matchAction);
}
return false;
}
-
- /**
- * Returns a string representation of this comparison operator.
- */
- @Override
- public final String toString() {
- return new StringBuilder(30).append(expression1).append('
').append(symbol()).append(' ')
- .append(expression2).toString();
- }
}
Copied:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java
(from r1831270,
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java)
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java?p2=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java&p1=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java&r1=1831270&r2=1831365&rev=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractComparisonOperator.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/AbstractUnaryOperator.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -17,146 +17,78 @@
package org.apache.sis.filter;
import java.io.Serializable;
-import org.opengis.filter.BinaryComparisonOperator;
-import org.opengis.filter.MatchAction;
+import org.opengis.filter.Filter;
import org.opengis.filter.expression.Expression;
/**
- * Base class for filters that compare exactly two values against each other.
- * The nature of the comparison is dependent on the subclass.
+ * Base class for filters performing operations on one value.
+ * The nature of the operation is dependent on the subclass.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
-abstract class AbstractComparisonOperator implements BinaryComparisonOperator,
Serializable {
+abstract class AbstractUnaryOperator implements Filter, Serializable {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = -1401452229232869720L;
+ private static final long serialVersionUID = -8183962550739028650L;
/**
- * The first of the two expressions to be compared by this operator.
+ * The expression to be used by this operator.
*
- * @see #getExpression1()
+ * @see #getExpression()
*/
- protected final Expression expression1;
+ protected final Expression expression;
/**
- * The second of the two expressions to be compared by this operator.
- *
- * @see #getExpression2()
- */
- protected final Expression expression2;
-
- /**
- * Whether comparisons are case sensitive.
- *
- * @see #isMatchingCase()
- */
- protected final boolean matchCase;
-
- /**
- * Specifies how the comparison predicate shall be evaluated for a
collection of values.
- *
- * @see #getMatchAction()
- */
- protected final MatchAction matchAction;
-
- /**
- * Creates a new binary comparison operator.
+ * Creates a new unary operator.
* It is caller responsibility to ensure that no argument is null.
*/
- AbstractComparisonOperator(final Expression expression1, final Expression
expression2,
- final boolean matchCase, final MatchAction
matchAction)
- {
- this.expression1 = expression1;
- this.expression2 = expression2;
- this.matchCase = matchCase;
- this.matchAction = matchAction;
+ AbstractUnaryOperator(final Expression expression) {
+ this.expression = expression;
}
/**
- * Returns the mathematical symbol for this comparison operator.
- * The symbol should be one of the following: {@literal < > ≤ ≥ = ≠}.
+ * Returns the mathematical symbol for this operator.
*/
protected abstract char symbol();
/**
- * Returns the first of the two expressions to be compared by this
operator.
+ * Returns the expressions to be used by this operator.
*/
- @Override
- public final Expression getExpression1() {
- return expression1;
+ public final Expression getExpression() {
+ return expression;
}
/**
- * Returns the second of the two expressions to be compared by this
operator.
+ * Returns a hash code value for this operator.
*/
@Override
- public final Expression getExpression2() {
- return expression2;
- }
-
- /**
- * Specifies whether comparisons are case sensitive.
- *
- * @return {@code true} if the comparisons are case sensitive, otherwise
{@code false}.
- */
- @Override
- public final boolean isMatchingCase() {
- return matchCase;
- }
-
- /**
- * Specifies how the comparison predicate shall be evaluated for a
collection of values.
- * Values can be {@link MatchAction#ALL ALL} if all values in the
collection shall satisfy the predicate,
- * {@link MatchAction#ANY ANY} if any of the value in the collection can
satisfy the predicate, or
- * {@link MatchAction#ONE ONE} if only one of the values in the collection
shall satisfy the predicate.
- *
- * @return how the comparison predicate shall be evaluated for a
collection of values.
- */
- @Override
- public final MatchAction getMatchAction() {
- return matchAction;
- }
-
- /**
- * Returns a hash code value for this comparison operator.
- */
- @Override
- public final int hashCode() {
- int hash = (31 * expression1.hashCode() + expression2.hashCode()) * 37
+ matchAction.hashCode();
- if (matchCase) hash = ~hash;
- return hash ^ symbol(); // Use the symbol as a way to
differentiate the subclasses.
+ public int hashCode() {
+ // We use the symbol as a way to differentiate the subclasses.
+ return expression.hashCode() ^ symbol();
}
/**
* Compares this operator with the given object for equality.
*/
@Override
- public final boolean equals(final Object obj) {
- if (obj == this) {
- return true;
- }
+ public boolean equals(final Object obj) {
if (obj != null && obj.getClass() == getClass()) {
- final AbstractComparisonOperator other =
(AbstractComparisonOperator) obj;
- return matchCase == other.matchCase &&
- expression1.equals(other.expression1) &&
- expression2.equals(other.expression2) &&
- matchAction.equals(other.matchAction);
+ return expression.equals(((AbstractUnaryOperator) obj).expression);
}
return false;
}
/**
- * Returns a string representation of this comparison operator.
+ * Returns a string representation of this operator.
*/
@Override
- public final String toString() {
- return new StringBuilder(30).append(expression1).append('
').append(symbol()).append(' ')
- .append(expression2).toString();
+ public String toString() {
+ return new
StringBuilder(30).append(expression).append(':').append(symbol()).toString();
}
}
Modified:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java?rev=1831365&r1=1831364&r2=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultFilterFactory.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -523,8 +523,9 @@ public class DefaultFilterFactory implem
* {@inheritDoc }
*/
@Override
- public PropertyIsNull isNull(final Expression expr) {
- throw new UnsupportedOperationException("Not supported yet.");
+ public PropertyIsNull isNull(final Expression expression) {
+ ArgumentChecks.ensureNonNull("expression", expression);
+ return new DefaultPropertyIsNull(expression);
}
/**
Modified:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java?rev=1831365&r1=1831364&r2=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -28,6 +28,7 @@ import org.opengis.filter.expression.Exp
* Filter operator that compares that its two sub-expressions are equal to
each other.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
@@ -36,7 +37,7 @@ final class DefaultPropertyIsEqualTo ext
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = -5549267988142039640L;
+ private static final long serialVersionUID = -5783347523815670017L;
/**
* Creates a new comparison operator.
Copied:
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java
(from r1831270,
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java)
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java?p2=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java&p1=sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java&r1=1831270&r2=1831365&rev=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsEqualTo.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-feature/src/main/java/org/apache/sis/filter/DefaultPropertyIsNull.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -16,80 +16,58 @@
*/
package org.apache.sis.filter;
-import java.util.Objects;
-import org.apache.sis.util.Numbers;
+import java.io.Serializable;
import org.opengis.filter.FilterVisitor;
-import org.opengis.filter.MatchAction;
-import org.opengis.filter.PropertyIsEqualTo;
+import org.opengis.filter.PropertyIsNull;
import org.opengis.filter.expression.Expression;
/**
- * Filter operator that compares that its two sub-expressions are equal to
each other.
+ * Filter operator that checks if an expression's value is {@code null}. A
{@code null}
+ * is equivalent to no value present. The value 0 is a valid value and is not
considered
+ * {@code null}.
*
* @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
-final class DefaultPropertyIsEqualTo extends AbstractComparisonOperator
implements PropertyIsEqualTo {
+final class DefaultPropertyIsNull extends AbstractUnaryOperator implements
PropertyIsNull, Serializable {
/**
* For cross-version compatibility.
*/
- private static final long serialVersionUID = -5549267988142039640L;
+ private static final long serialVersionUID = 3942075458551232678L;
/**
- * Creates a new comparison operator.
+ * Creates a new operator.
* It is caller responsibility to ensure that no argument is null.
*/
- DefaultPropertyIsEqualTo(Expression expression1, Expression expression2,
boolean matchCase, MatchAction matchAction) {
- super(expression1, expression2, matchCase, matchAction);
+ DefaultPropertyIsNull(final Expression expression) {
+ super(expression);
}
/**
- * Returns the mathematical symbol for this comparison operator.
+ * Returns the null symbol, to be used in string representation.
*/
@Override
protected char symbol() {
- return '=';
+ return '∅';
}
/**
- * Determines if the test represented by this filter passed.
- *
- * @todo Use locale-sensitive {@link java.text.Collator} for string
comparisons.
+ * Returns {@code true} if the given value evaluates to {@code null}.
*/
@Override
- public boolean evaluate(Object object) {
- final Object r1 = expression1.evaluate(object);
- final Object r2 = expression2.evaluate(object);
- if (Objects.equals(r1, r2)) {
- return true;
- } else if (r1 instanceof Number && r2 instanceof Number) {
- @SuppressWarnings("unchecked") final Class<? extends Number> c1 =
(Class<? extends Number>) r1.getClass();
- @SuppressWarnings("unchecked") final Class<? extends Number> c2 =
(Class<? extends Number>) r2.getClass();
- if (c1 != c2) {
- final Class<? extends Number> c = Numbers.widestClass(c1, c2);
- return Numbers.cast((Number) r1, c).equals(
- Numbers.cast((Number) r2, c));
- }
- } else if (r1 instanceof CharSequence && r2 instanceof CharSequence) {
- final String s1 = r1.toString();
- final String s2 = r2.toString();
- if (!matchCase) {
- return s1.equalsIgnoreCase(s2);
- } else if (r1 != s1 || r2 != s2) {
- return s1.equals(s2);
- }
- }
- return false;
+ public boolean evaluate(final Object object) {
+ return expression.evaluate(object) == null;
}
/**
* Accepts a visitor.
*/
@Override
- public Object accept(FilterVisitor visitor, Object extraData) {
+ public Object accept(final FilterVisitor visitor, final Object extraData) {
return visitor.visit(this, extraData);
}
}
Modified:
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java?rev=1831365&r1=1831364&r2=1831365&view=diff
==============================================================================
---
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
[UTF-8] (original)
+++
sis/branches/JDK8/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/JoinFeatureSet.java
[UTF-8] Thu May 10 19:08:39 2018
@@ -16,431 +16,548 @@
*/
package org.apache.sis.internal.storage;
-import java.util.Arrays;
-import java.util.Iterator;
+import java.util.Map;
+import java.util.Collections;
import java.util.Spliterator;
-import java.util.Spliterators;
+import java.util.function.Consumer;
import java.util.stream.Stream;
import java.util.stream.StreamSupport;
-import org.apache.sis.feature.builder.FeatureTypeBuilder;
+import org.opengis.util.GenericName;
+import org.opengis.geometry.Envelope;
+import org.apache.sis.feature.FeatureOperations;
+import org.apache.sis.feature.DefaultFeatureType;
+import org.apache.sis.feature.DefaultAssociationRole;
import org.apache.sis.filter.DefaultFilterFactory;
import org.apache.sis.internal.feature.AttributeConvention;
import org.apache.sis.internal.storage.query.SimpleQuery;
-import org.apache.sis.metadata.iso.DefaultMetadata;
-import org.apache.sis.metadata.iso.citation.DefaultCitation;
-import org.apache.sis.metadata.iso.identification.DefaultDataIdentification;
-import org.apache.sis.referencing.NamedIdentifier;
import org.apache.sis.storage.DataStore;
import org.apache.sis.storage.DataStoreException;
import org.apache.sis.storage.FeatureSet;
+import org.apache.sis.util.ArraysExt;
import org.apache.sis.util.collection.BackingStoreException;
-import org.apache.sis.util.iso.SimpleInternationalString;
+import org.apache.sis.util.collection.Containers;
import org.apache.sis.util.logging.WarningListeners;
+
+// Branch-dependent imports
import org.opengis.feature.Feature;
import org.opengis.feature.FeatureType;
+import org.opengis.feature.Operation;
+import org.opengis.feature.PropertyType;
+import org.opengis.filter.Filter;
import org.opengis.filter.FilterFactory;
-import org.opengis.filter.MatchAction;
import org.opengis.filter.PropertyIsEqualTo;
import org.opengis.filter.expression.Expression;
-import org.opengis.geometry.Envelope;
-import org.opengis.metadata.Metadata;
-import org.opengis.util.GenericName;
+
/**
- * A join feature set merges features from two different sources following a
- * SQL Join condition.
+ * Features containing association to features from two different sources,
joined by a SQL-like {@code JOIN} condition.
+ * Each feature in this {@code FeatureSet} contains two or three properties:
*
- * <p>
- * This implementation is read-only.
- * </p>
+ * <ul>
+ * <li>An optional identifier created from the identifiers of the left and
right features.</li>
+ * <li>Zero or one association to a "left" feature.</li>
+ * <li>Zero or one association to a "right" feature.</li>
+ * </ul>
*
- * @author Johann Sorel (Geomatys)
+ * The left and right features appear together in an {@code JoinFeatureSet}
instance when a value from
+ * {@code leftProperty} in the first feature is equal to a value from {@code
rightProperty} in the second feature.
+ *
+ * <div class="section">Implementation note</div>
+ * If iterations in one feature set is cheaper than iterations in the other
feature set, then the "costly" or larger
+ * {@code FeatureSet} should be on the left side and the "cheap" {@code
FeatureSet} should be on the right side.
+ *
+ * <p>This implementation is read-only.</p>
+ *
+ * @author Johann Sorel (Geomatys)
+ * @author Martin Desruisseaux (Geomatys)
* @version 1.0
* @since 1.0
* @module
*/
-public class JoinFeatureSet extends AbstractFeatureSet implements FeatureSet {
+public class JoinFeatureSet extends AbstractFeatureSet {
/**
- * Join operation type.
+ * Specifies whether values on both sides are required (inner join), or
only one side (outer join).
*/
public enum Type {
/**
- * Both side must have a value to be included.
+ * Only records having a value on both side will be included.
+ * The {@link JoinFeatureSet} {@code "left"} and {@code "right"}
properties will never be null.
*/
- INNER,
+ INNER(false, false),
/**
- * A least left side must have a value to be included.
+ * All records from the left side will be included. If there is no
matching feature on the right side,
+ * then the {@link JoinFeatureSet} {@code "right"} property will be
{@code null}.
*/
- LEFT_OUTER,
+ LEFT_OUTER(true, false),
/**
- * A least right side must have a value to be included.
+ * All records from the right side will be included. If there is no
matching feature on the left side,
+ * then the {@link JoinFeatureSet} {@code "left"} property will be
{@code null}.
*/
- RIGHT_OUTER
- }
+ RIGHT_OUTER(true, true);
- private final FeatureSet left;
- private final FeatureSet right;
- private String leftAlias;
- private String rightAlias;
- private final Type joinType;
- private final PropertyIsEqualTo condition;
+ /**
+ * Whether to include all "main" feature instances even if there is no
match in the other side.
+ * This is {@code true} for outer joins and {@code false} for inner
joins.
+ */
+ final boolean isOuterJoin;
- private final FilterFactory factory;
+ /**
+ * {@code true} if the "main" side is the right side instead than the
left side.
+ * See {@link JoinFeatureSet.Iterator} for a definition of "main side".
+ */
+ final boolean swapSides;
- //cache
- private FeatureType type;
+ /**
+ * Creates an enumeration.
+ */
+ private Type(final boolean isOuterJoin, final boolean swapSides) {
+ this.isOuterJoin = isOuterJoin;
+ this.swapSides = swapSides;
+ }
+ /**
+ * Returns the minimum occurrences for properties on the left or right
side.
+ *
+ * @param right {@code false} for the left side, or {@code true} for
the right side.
+ */
+ final int minimumOccurs(final boolean right) {
+ return !isOuterJoin | (swapSides == right) ? 1 : 0;
+ }
- public JoinFeatureSet(final WarningListeners<DataStore> listeners,
FeatureSet left,
- String leftAlias, FeatureSet right, String rightAlias, Type
joinType, PropertyIsEqualTo condition)
- {
- super(listeners);
- this.left = left;
- this.right = right;
- this.leftAlias = leftAlias;
- this.rightAlias = rightAlias;
- this.joinType = joinType;
- this.condition = condition;
- factory = new DefaultFilterFactory();
+ /**
+ * Returns the enumeration value for the given characteristics.
+ */
+ static Type valueOf(final boolean isOuterJoin, final boolean
swapSides) {
+ return isOuterJoin ? (swapSides ? RIGHT_OUTER : LEFT_OUTER) :
INNER;
+ }
}
/**
- * Gets the join condition.
- *
- * @return Filter
+ * The type of features included in this set. Contains two associations as
described in class javadoc.
*/
- public PropertyIsEqualTo getJoinCondition() {
- return condition;
- }
+ private final FeatureType type;
+
+ /**
+ * The first source of features.
+ */
+ public final FeatureSet left;
/**
- * Gets the join type.
+ * The second source of features.
+ */
+ public final FeatureSet right;
+
+ /**
+ * Name of the associations to the {@link #left} features.
+ * This may be the name of the {@link #left} feature type, but not
necessarily.
+ */
+ private final String leftName;
+
+ /**
+ * Name of the associations to the {@link #right} features.
+ * This may be the name of the {@link #right} feature type, but not
necessarily.
+ */
+ private final String rightName;
+
+ /**
+ * {@code true} if the "main" side is the right side instead than the left
side.
+ * See {@link JoinFeatureSet.Iterator} for a definition of "main side".
+ */
+ private final boolean swapSides;
+
+ /**
+ * Whether to include all "main" feature instances even if there is no
match in the other side.
+ * This is {@code true} for outer joins and {@code false} for inner joins.
+ */
+ private final boolean isOuterJoin;
+
+ /**
+ * The join condition in the form <var>property from left feature</var> =
<var>property from right feature</var>.
+ * This condition specifies also if the comparison is {@linkplain
PropertyIsEqualTo#isMatchingCase() case sensitive}
+ * and {@linkplain PropertyIsEqualTo#getMatchAction() how to compare
multi-values}.
+ */
+ public final PropertyIsEqualTo condition;
+
+ /**
+ * The factory to use for creating {@code Query} expressions for
retrieving subsets of feature sets.
+ */
+ private final FilterFactory factory;
+
+ /**
+ * Creates a new feature set joining the two given sets. The {@code
featureInfo} map defines the name,
+ * description or other information for the {@code FeatureType} created by
this method. It can contain all
+ * the properties described in {@link
org.apache.sis.feature.DefaultFeatureType} plus the following ones:
+ *
+ * <ul>
+ * <li>{@code "identifierDelimiter"} — string to insert between left and
right identifiers in the identifiers
+ * generated by the join operation. If this property is not specified,
then no identifier will be generated.</li>
+ * <li>{@code "identifierPrefix"} — string to insert at the beginning of
join identifiers (optional).</li>
+ * <li>{@code "identifierSuffix"} — string to insert at the end of join
identifiers (optional).</li>
+ * </ul>
*
- * @return join type
+ * @param listeners the set of registered warning listeners for the
data store, or {@code null} if none.
+ * @param left the first source of features. This is often (but
not necessarily) the largest set.
+ * @param leftAlias name of the associations to the {@code left}
features, or {@code null} for a default name.
+ * @param right the second source of features. Should be the set
in which iterations are cheapest.
+ * @param rightAlias name of the associations to the {@code right}
features, or {@code null} for a default name.
+ * @param joinType whether values on both sides are required (inner
join), or only one side (outer join).
+ * @param condition join condition as <var>property from left
feature</var> = <var>property from right feature</var>.
+ * @param featureInfo information about the {@link FeatureType} of this
+ * @throws DataStoreException if an error occurred while creating the
feature set.
*/
- public Type getJoinType() {
- return joinType;
+ public JoinFeatureSet(final WarningListeners<DataStore> listeners,
+ final FeatureSet left, String leftAlias,
+ final FeatureSet right, String rightAlias,
+ final Type joinType, final PropertyIsEqualTo
condition,
+ Map<String,?> featureInfo)
+ throws DataStoreException
+ {
+ super(listeners);
+ final FeatureType leftType = left.getType();
+ final FeatureType rightType = right.getType();
+ final GenericName leftName = leftType.getName();
+ final GenericName rightName = rightType.getName();
+ if (leftAlias == null) leftAlias = leftName.toString();
+ if (rightAlias == null) rightAlias = rightName.toString();
+ this.left = left;
+ this.right = right;
+ this.leftName = leftAlias;
+ this.rightName = rightAlias;
+ this.swapSides = joinType.swapSides;
+ this.isOuterJoin = joinType.isOuterJoin;
+ this.condition = condition;
+ this.factory = new DefaultFilterFactory(); // TODO: replace
by some static instance?
+ /*
+ * We could build the FeatureType only when first needed, but the type
is required by the iterators.
+ * Since we are going to need the type for any use of this
JoinFeatureSet, better to create it now.
+ */
+ PropertyType[] properties = new PropertyType[] {
+ new DefaultAssociationRole(name(leftAlias), leftType,
joinType.minimumOccurs(false), 1),
+ new DefaultAssociationRole(name(rightAlias), rightType,
joinType.minimumOccurs(true), 1)
+ };
+ final String identifierDelimiter = Containers.property(featureInfo,
"identifierDelimiter", String.class);
+ if (identifierDelimiter != null &&
AttributeConvention.hasIdentifier(leftType)
+ &&
AttributeConvention.hasIdentifier(rightType))
+ {
+ final Operation identifier = FeatureOperations.compound(
+ name(AttributeConvention.IDENTIFIER_PROPERTY),
identifierDelimiter,
+ Containers.property(featureInfo, "identifierPrefix",
String.class),
+ Containers.property(featureInfo, "identifierSuffix",
String.class), properties);
+ properties = ArraysExt.insert(properties, 0, 1);
+ properties[0] = identifier;
+ }
+ if (featureInfo == null) {
+ featureInfo = name(leftName.tip().toString() + '-' +
rightName.tip());
+ }
+ type = new DefaultFeatureType(featureInfo, false, null, properties);
}
/**
- * Gets the left feature source.
- *
- * @return left join feature set
+ * Creates a minimal {@code properties} map for feature type or property
type constructors.
+ * This minimalist map contain only the mandatory entry, which is the name.
*/
- public FeatureSet getLeft() {
- return left;
+ private static Map<String,?> name(final Object name) {
+ return Collections.singletonMap(DefaultFeatureType.NAME_KEY, name);
}
/**
- * Gets the right feature source.
+ * Specifies whether values on both sides are required (inner join), or
only one side (outer join).
*
- * @return right join feature set
+ * @return whether values on both sides are required (inner join), or only
one side (outer join).
*/
- public FeatureSet getRight() {
- return right;
+ public Type getJoinType() {
+ return Type.valueOf(isOuterJoin, swapSides);
}
+ /**
+ * Returns a description of properties that are common to all features in
this dataset.
+ * This type may contain one identifier and always contains two
associations,
+ * to the {@linkplain #left} and {@link #right} set of features
respectively.
+ *
+ * @return a description of properties that are common to all features in
this dataset.
+ */
@Override
- public FeatureType getType() throws DataStoreException {
- if (type == null) {
- final FeatureType leftType = left.getType();
- final FeatureType rightType = right.getType();
- final GenericName leftName = leftType.getName();
- final GenericName rightName = rightType.getName();
- if (leftAlias == null) leftAlias = leftName.toString();
- if (rightAlias == null) rightAlias = rightName.toString();
-
- final FeatureTypeBuilder ftb = new FeatureTypeBuilder();
-
ftb.addAttribute(String.class).setName(AttributeConvention.IDENTIFIER_PROPERTY).setDefaultValue("");
-
ftb.addAssociation(leftType).setName(leftAlias).setMinimumOccurs(0).setMaximumOccurs(1);
-
ftb.addAssociation(rightType).setName(rightAlias).setMinimumOccurs(0).setMaximumOccurs(1);
- ftb.setName(leftName.tip().toString() + '-' +
rightName.tip().toString());
- type = ftb.build();
- }
+ public FeatureType getType() {
return type;
}
- @Override
- public Metadata getMetadata() throws DataStoreException {
- final FeatureType type = getType();
- final DefaultMetadata metadata = new DefaultMetadata();
- final DefaultDataIdentification ident = new
DefaultDataIdentification();
- final DefaultCitation citation = new DefaultCitation();
- citation.setTitle(new
SimpleInternationalString(type.getName().toString()));
- citation.setIdentifiers(Arrays.asList(new
NamedIdentifier(type.getName())));
- ident.setCitation(citation);
- metadata.setIdentificationInfo(Arrays.asList(ident));
- return metadata;
- }
-
/**
- * Envelope is not stored or computed.
+ * Returns {@code null} since computing an envelope would be costly for
this set.
*
- * @return always null
- * @throws DataStoreException
+ * @return always {@code null} in default implementation.
+ *
+ * @todo Revisit the method contract by allowing the envelope to be only
an estimation, potentially larger.
*/
@Override
- public Envelope getEnvelope() throws DataStoreException {
+ public Envelope getEnvelope() {
return null;
}
+ /**
+ * Returns a stream of all features contained in this dataset.
+ *
+ * @param parallel {@code true} for a parallel stream (if supported), or
{@code false} for a sequential stream.
+ * @return all features contained in this dataset.
+ * @throws DataStoreException if an error occurred while creating the
stream.
+ */
@Override
- public Stream<Feature> features(boolean parallel) throws
DataStoreException {
- final JoinIterator ite;
- switch (joinType) {
- case INNER : ite = new JoinInnerRowIterator(); break;
- case LEFT_OUTER : ite = new JoinOuterRowIterator(true); break;
- case RIGHT_OUTER : ite = new JoinOuterRowIterator(false); break;
- default:
- throw new IllegalArgumentException("Unknown Join type : " +
joinType);
- }
- final Stream<Feature> stream = StreamSupport.stream(
- Spliterators.spliteratorUnknownSize(ite, Spliterator.ORDERED),
- false);
- stream.onClose(ite::close);
- return stream;
+ public Stream<Feature> features(final boolean parallel) throws
DataStoreException {
+ final Iterator it = new Iterator();
+ return StreamSupport.stream(it, parallel).onClose(it);
}
/**
- * Aggregate all feature from selectors to a single complex feature.
- *
- * @return aggregated features
- * @throws DataStoreException
+ * Creates a new features containing an association to the two given
features.
+ * The {@code main} feature can not be null (this is not verified).
*/
- private Feature toFeature(final Feature left, final Feature right) throws
DataStoreException{
- final FeatureType type = getType(); //force creating type.
- final Feature f = type.newInstance();
-
- String id = "";
-
- if (left != null) {
- id +=
left.getPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString());
- f.setPropertyValue(leftAlias,left);
- }
- if (right != null) {
- if (left != null) id += " ";
- id +=
right.getPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString());
- f.setPropertyValue(rightAlias,right);
+ private Feature join(Feature main, Feature filtered) {
+ if (swapSides) {
+ final Feature t = main;
+ main = filtered;
+ filtered = t;
}
- f.setPropertyValue(AttributeConvention.IDENTIFIER_PROPERTY.toString(),
id);
+ final Feature f = type.newInstance();
+ f.setPropertyValue(leftName, main);
+ f.setPropertyValue(rightName, filtered);
return f;
}
- private interface JoinIterator extends Iterator<Feature>, AutoCloseable {
+ /**
+ * Iterator over the features resulting from the inner or outer join
operation.
+ * The {@link #run()} method disposes the resources.
+ */
+ private final class Iterator implements Spliterator<Feature>,
Consumer<Feature>, Runnable {
+ /**
+ * The main stream or a split iterator to close when the {@link
#run()} method will be invoked.
+ * This is initially the stream from which {@link #mainIterator} has
been created. However if
+ * {@link #trySplit()} has been invoked, then this handler may be the
other {@code Iterator}
+ * instance which itself contains a reference to the stream to close,
thus forming a chain.
+ */
+ private Runnable mainCloseHandler;
- @Override
- public default void close() {}
+ /**
+ * An iterator over all features in the "main" (usually left) side.
The "main" side is the side which
+ * may include all features: in a "left outer join" this is the left
side, and in a "right outer join"
+ * this is the right side. For inner join we arbitrarily take the left
side in accordance with public
+ * class javadoc, which suggests to put the most costly or larger set
on the left side.
+ *
+ * <p>Only one iteration will be performed on those features,
contrarily to the other side where we may
+ * iterate over the same elements many times.</p>
+ */
+ private final Spliterator<Feature> mainIterator;
- }
+ /**
+ * A feature fetched from the {@link #mainIterator}. The join
operation will match this feature with
+ * zero, one or more features from the other side. A {@code null}
value means that this feature needs
+ * to be retrieved with {@code mainIterator.tryAdvance(…)}.
+ */
+ private Feature mainFeature;
- /**
- * Iterate on both collections with an Inner join condition.
- */
- private class JoinInnerRowIterator implements JoinIterator {
+ /**
+ * The stream over features in the other (usually right) side. A new
stream will be created every time a new
+ * feature from the main side is processed. For this reason, it should
be the cheapest stream if possible.
+ */
+ private Stream<Feature> filteredStream;
+
+ /**
+ * Iterator for the {@link #filteredStream}. A new iterator will be
recreated every time a new feature
+ * from the main side is processed.
+ */
+ private Spliterator<Feature> filteredIterator;
+
+ /**
+ * A feature fetched from the {@link #filteredIterator}, or {@code
null} if none.
+ */
+ private Feature filteredFeature;
- private final Stream<Feature> leftStream;
- private final Iterator<Feature> leftIterator;
- private Stream<Feature> rightStream;
- private Iterator<Feature> rightIterator;
- private Feature leftFeature;
- private Feature combined;
-
- JoinInnerRowIterator() throws DataStoreException {
- leftStream = left.features(false);
- leftIterator = leftStream.iterator();
+ /**
+ * Creates a new iterator. We do not use parallelized {@code
mainStream} here because the {@code accept(…)}
+ * methods used by this {@code Iterator} can not be invoked
concurrently by different threads. It does not
+ * present parallelization at a different level since this {@code
Iterator} supports {@link #trySplit()},
+ * so the {@link Stream} wrapping it can use parallelization.
+ */
+ Iterator() throws DataStoreException {
+ final Stream<Feature> mainStream = (swapSides ? right :
left).features(false);
+ mainCloseHandler = mainStream::close;
+ mainIterator = mainStream.spliterator();
}
- @Override
- public Feature next() {
- try {
- searchNext();
- } catch (DataStoreException ex) {
- throw new BackingStoreException(ex);
- }
- Feature f = combined;
- combined = null;
- return f;
+ /**
+ * Creates an iterator resulting from the call to {@link #trySplit()}.
+ */
+ private Iterator(final Spliterator<Feature> it) {
+ mainIterator = it;
}
+ /**
+ * If this iterator can be partitioned, returns a spliterator covering
a prefix of the feature set.
+ * Upon return from this method, this iterator will cover a suffix of
the feature set.
+ * Returns {@code null} if this iterator can not be partitioned.
+ */
@Override
- public void close() {
- leftStream.close();
- if (rightStream != null) {
- rightStream.close();
- }
+ public Spliterator<Feature> trySplit() {
+ final Spliterator<Feature> s = mainIterator.trySplit();
+ if (s == null) {
+ return null;
+ }
+ final Iterator it = new Iterator(s);
+ it.mainCloseHandler = mainCloseHandler;
+ mainCloseHandler = it;
+ return it;
}
+ /**
+ * Specifies that the iterator will return only non-null elements.
Whether those elements will
+ * be ordered depends on whether the main iterator provides ordered
elements in the first place.
+ *
+ * <p><b>NOTE:</b> to be strict, we should check if the "filtered"
stream is also ordered. But this
+ * is more difficult to check. Current implementation assumes that if
the "mean" stream is ordered,
+ * then the other stream is ordered too. Furthermore the {@link
#trySplit()} method works only on
+ * the main stream, so at least the {@code trySplit} requirement about
prefix and suffix order is
+ * still fulfill even if the other stream is unordered.</p>
+ */
@Override
- public boolean hasNext() {
- try {
- searchNext();
- } catch (DataStoreException ex) {
- throw new BackingStoreException(ex);
- }
- return combined != null;
+ public int characteristics() {
+ return (mainIterator.characteristics() & ORDERED) | NONNULL;
}
- private void searchNext() throws DataStoreException {
- if (combined != null) return;
-
- final PropertyIsEqualTo equal = getJoinCondition();
- final Expression leftProperty = equal.getExpression1();
- final Expression rightProperty = equal.getExpression2();
+ /**
+ * Estimated size is unknown.
+ */
+ @Override
+ public long estimateSize() {
+ return Long.MAX_VALUE;
+ }
- //we might have several right features for one left
- if (leftFeature != null && rightIterator != null) {
- while (combined == null && rightIterator.hasNext()) {
- final Feature rightRes = rightIterator.next();
- combined = toFeature(leftFeature, rightRes);
- }
- }
- if (rightIterator != null && !rightIterator.hasNext()){
- //no more results in right iterator, close iterator
- rightStream.close();
- rightStream = null;
- rightIterator = null;
- }
- while (combined == null && leftIterator.hasNext()) {
- leftFeature = leftIterator.next();
- final Object leftValue = leftProperty.evaluate(leftFeature);
- if (rightIterator == null) {
- final SimpleQuery rightQuery = new SimpleQuery();
- rightQuery.setFilter(factory.equal(rightProperty,
factory.literal(leftValue), true, MatchAction.ALL));
- rightStream = right.subset(rightQuery).features(false);
- rightIterator = rightStream.iterator();
- }
- while (combined == null && rightIterator.hasNext()) {
- final Feature rightRow = rightIterator.next();
- combined = toFeature(leftFeature, rightRow);
- }
- if (combined == null) {
- //no more results in right iterator, close iterator
- rightStream.close();
- rightStream = null;
- rightIterator = null;
- }
+ /**
+ * Closes the streams used by this iterator, together with the streams
used by any spliterator
+ * created by {@link #trySplit()}. This method is registered to {@link
Stream#onClose(Runnable)}.
+ */
+ @Override
+ public void run() {
+ closeFilteredIterator();
+ final Runnable toClose = mainCloseHandler;
+ if (toClose != null) {
+ mainCloseHandler = null; // Cleared first in case
of error.
+ toClose.run();
}
}
- }
- /**
- * Iterate on both collections with an outer join condition.
- */
- private class JoinOuterRowIterator implements JoinIterator {
-
- private final Stream<Feature> primeStream;
- private final Iterator<Feature> primeIterator;
- private Stream<Feature> secondStream;
- private Iterator<Feature> secondIterator;
-
- private final boolean leftJoint;
- private Feature primeFeature;
- private Feature nextFeature;
-
- JoinOuterRowIterator(final boolean leftJoint) throws
DataStoreException{
- this.leftJoint = leftJoint;
- if (leftJoint) {
- primeStream = left.features(false);
- } else {
- primeStream = right.features(false);
+ /**
+ * Invoked when iteration on the filtered stream ended, before to move
on the next feature of the main stream.
+ * This method is idempotent: it has no effect if the stream is
already closed.
+ */
+ private void closeFilteredIterator() {
+ final Stream<Feature> stream = filteredStream;
+ filteredStream = null; // Cleared before call to
close() in case of error.
+ filteredIterator = null;
+ filteredFeature = null; // Used as a sentinel
value by this.forEachRemaining(…).
+ mainFeature = null; // Indicate that we will
need to advance in mainIterator.
+ if (stream != null) {
+ stream.close();
}
- primeIterator = primeStream.iterator();
}
- @Override
- public Feature next() {
+ /**
+ * Creates a new iterator over the filtered set of features (usually
the right side).
+ * The filtering condition is determined by the current {@link
#mainFeature}.
+ */
+ private void createFilteredIterator() {
+ Expression expression1 = condition.getExpression1();
+ Expression expression2 = condition.getExpression2();
+ FeatureSet filteredSet = right;
+ if (swapSides) {
+ filteredSet = left;
+ Expression t = expression2;
+ expression2 = expression1;
+ expression1 = t;
+ }
+ final Object mainValue = expression1.evaluate(mainFeature);
+ final Filter filter;
+ if (mainValue != null) {
+ filter = factory.equal(expression2, factory.literal(mainValue),
+ condition.isMatchingCase(),
condition.getMatchAction());
+ } else {
+ filter = factory.isNull(expression2);
+ }
+ final SimpleQuery query = new SimpleQuery();
+ query.setFilter(filter);
try {
- searchNext();
- } catch (DataStoreException ex) {
- throw new BackingStoreException(ex);
+ filteredStream = filteredSet.subset(query).features(false);
+ } catch (DataStoreException e) {
+ throw new BackingStoreException(e);
}
- Feature f = nextFeature;
- nextFeature = null;
- return f;
+ filteredIterator = filteredStream.spliterator();
}
+ /**
+ * Executes the given action on all remaining features in the {@code
JoinFeatureSet}.
+ */
@Override
- public void close() {
- primeStream.close();
- if (secondStream != null) {
- secondStream.close();
- }
+ public void forEachRemaining(final Consumer<? super Feature> action) {
+ final Consumer<Feature> forFiltered = (final Feature feature) -> {
+ if (feature != null) {
+ action.accept(join(mainFeature, filteredFeature =
feature));
+ }
+ };
+ final Consumer<Feature> forMain = (final Feature feature) -> {
+ if (feature != null) {
+ mainFeature = feature;
+ createFilteredIterator();
+ filteredIterator.forEachRemaining(forFiltered);
+ final boolean none = (filteredFeature == null);
+ closeFilteredIterator();
+ if (none && isOuterJoin) {
+ action.accept(join(feature, null));
+ }
+ // Do not close the main stream since it may be in use by
other Spliterators.
+ }
+ };
+ forMain.accept(mainFeature); // In case some
'tryAdvance' has been invoked before.
+ mainIterator.forEachRemaining(forMain);
}
+ /**
+ * Callback for {@code Spliterator.tryAdvance(this)} on {@link
#filteredIterator}.
+ * Used by {@link #tryAdvance(Consumer)} implementation only.
+ */
@Override
- public boolean hasNext() {
- try {
- searchNext();
- } catch (DataStoreException ex) {
- throw new BackingStoreException(ex);
- }
- return nextFeature != null;
+ public void accept(final Feature feature) {
+ filteredFeature = feature;
}
- private void searchNext() throws DataStoreException {
- if (nextFeature != null) return;
-
- final PropertyIsEqualTo equal = getJoinCondition();
- final Expression leftProperty = equal.getExpression1();
- final Expression rightProperty = equal.getExpression2();
-
- //we might have several right features for one left
- if (primeFeature != null && secondIterator != null) {
- while (nextFeature == null && secondIterator.hasNext()) {
- final Feature secondCandidate = secondIterator.next();
- nextFeature = checkValid(primeFeature, secondCandidate,
leftJoint);
- }
- }
- while (nextFeature == null && primeIterator.hasNext()) {
- primeFeature = primeIterator.next();
- if (secondIterator != null) {
- secondStream.close();
- secondStream = null;
- secondIterator = null;
+ /**
+ * Executes the given action on the next feature in the {@code
JoinFeatureSet}.
+ */
+ @Override
+ public boolean tryAdvance(final Consumer<? super Feature> action) {
+ for (;;) {
+ if (mainFeature == null) {
+ do if (!mainIterator.tryAdvance(this)) {
+ return false;
+ } while (filteredFeature == null);
+ mainFeature = filteredFeature;
+ filteredFeature = null;
}
- final Object primeValue;
- if (leftJoint) {
- primeValue = leftProperty.evaluate(primeFeature);
- } else {
- primeValue = rightProperty.evaluate(primeFeature);
+ if (filteredIterator == null) {
+ createFilteredIterator();
}
- if (secondIterator == null) {
- final SimpleQuery query = new SimpleQuery();
- if (leftJoint) {
- query.setFilter(factory.equal(rightProperty,
factory.literal(primeValue), true, MatchAction.ALL));
- secondStream = right.subset(query).features(false);
- secondIterator = secondStream.iterator();
- } else {
- query.setFilter(factory.equal(leftProperty,
factory.literal(primeValue), true, MatchAction.ALL));
- secondStream = left.subset(query).features(false);
- secondIterator = secondStream.iterator();
+ final boolean none = (filteredFeature == null);
+ while (filteredIterator.tryAdvance(this)) {
+ if (filteredFeature != null) {
+ action.accept(join(mainFeature, filteredFeature));
+ return true;
}
}
- while (nextFeature == null && secondIterator.hasNext()) {
- final Feature rightRow = secondIterator.next();
- nextFeature = checkValid(primeFeature, rightRow,leftJoint);
- }
- if (nextFeature == null) {
- //outer left effect, no right match but still we must
return the left side
- if (leftJoint) {
- nextFeature = toFeature(primeFeature,null);
- } else {
- nextFeature = toFeature(null,primeFeature);
- }
+ final Feature feature = mainFeature;
+ closeFilteredIterator();
+ if (none && isOuterJoin) {
+ action.accept(join(feature, null));
+ return true;
}
}
}
-
- private Feature checkValid(final Feature left, final Feature right,
final boolean leftJoin) throws DataStoreException{
- final Feature candidate;
- if (leftJoin) {
- candidate = toFeature(left,right);
- } else {
- candidate = toFeature(right,left);
- }
- return candidate;
- }
}
}