Author: desruisseaux
Date: Fri Feb 6 21:48:57 2015
New Revision: 1657973
URL: http://svn.apache.org/r1657973
Log:
Ported OperationMethodSet, to be needed by DefaultMathTransformFactory.
Added tests (this is new code).
Added:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java
(with props)
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
(with props)
Modified:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
Added:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java?rev=1657973&view=auto
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java
(added)
+++
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java
[UTF-8] Fri Feb 6 21:48:57 2015
@@ -0,0 +1,209 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.sis.referencing.operation.transform;
+
+import java.util.List;
+import java.util.ArrayList;
+import java.util.Set;
+import java.util.AbstractSet;
+import java.util.Iterator;
+import java.util.NoSuchElementException;
+import java.util.ServiceLoader;
+import org.opengis.referencing.operation.SingleOperation;
+import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.referencing.operation.DefaultOperationMethod;
+
+
+/**
+ * An immutable and thread-safe set containing the operation methods given by
an {@link Iterable}.
+ * Initial iteration is synchronized on the given {@code Iterable} and the
result is cached.
+ *
+ * {@section Rational}
+ * We use this class instead than copying the {@link OperationMethod}
instances in a {@link java.util.HashSet}
+ * in order to allow deferred {@code OperationMethod} instantiation, for
example in the usual case where the
+ * iterable is a {@link java.util.ServiceLoader}: we do not invoke {@link
Iterator#next()} before needed.
+ *
+ * {@section Limitations}
+ * The usual {@link Set} methods like {@code contains(Object)} are inefficient
as they may require a traversal
+ * of all elements in this set.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.6
+ * @version 0.6
+ * @module
+ */
+final class OperationMethodSet extends AbstractSet<OperationMethod> {
+ /**
+ * The operation type we are looking for.
+ */
+ private final Class<? extends SingleOperation> type;
+
+ /**
+ * The {@link DefaultMathTransformFactory#methods} used for fetching the
initial methods.
+ * We need this reference for locking purpose.
+ */
+ private final Iterable<? extends OperationMethod> methods;
+
+ /**
+ * Iterator over {@link #methods} elements. All usage of this iterator
must be synchronized on {@link #methods}.
+ * Will be set to {@code null} when the iteration is over.
+ */
+ private Iterator<? extends OperationMethod> methodIterator;
+
+ /**
+ * The methods returned by the first iteration.
+ */
+ private final List<OperationMethod> cachedMethods;
+
+ /**
+ * Constructs a set wrapping the given iterable.
+ * The caller musts holds the lock on {@code methods} when invoking this
constructor.
+ *
+ * @param type The type of coordinate operation for which to retain
methods.
+ * @param methods The {@link DefaultMathTransformFactory#methods} used for
fetching the initial methods.
+ */
+ OperationMethodSet(final Class<? extends SingleOperation> type,
+ final Iterable<? extends OperationMethod> methods)
+ {
+ this.type = type;
+ this.methods = methods;
+ cachedMethods = new ArrayList<>();
+ reset();
+ }
+
+ /**
+ * Invoked on construction time, or when the service loader has been
reloaded.
+ * The caller musts holds the lock on {@code methods} when invoking this
method.
+ */
+ final synchronized void reset() {
+ assert Thread.holdsLock(methods);
+ cachedMethods.clear();
+ methodIterator = methods.iterator();
+ if (!methodIterator.hasNext()) {
+ methodIterator = null;
+ }
+ }
+
+ /**
+ * Transfers the next element from {@link #methodIterator} to {@link
#cachedMethods}.
+ *
+ * @return {@code true} if the transfer has been done, or {@code false} if
the next
+ * method has been skipped because its operation type is not the
expected one
+ * or because the element has already been added in a previous
transfer.
+ */
+ private boolean transfer() {
+ assert Thread.holdsLock(this);
+ final OperationMethod method;
+ synchronized (methods) {
+ method = methodIterator.next();
+ if (!methodIterator.hasNext()) {
+ methodIterator = null;
+ }
+ }
+ if (method instanceof DefaultOperationMethod) {
+ if (!type.isAssignableFrom(((DefaultOperationMethod)
method).getOperationType())) {
+ return false;
+ }
+ }
+ /*
+ * ServiceLoader guarantees that the iteration does not contain
duplicated elements.
+ * The Set contract provides similar guarantee. For other types (which
should be very
+ * uncommon), we check for duplicated elements as a safety.
+ *
+ * Note that in the vast majority of cases, 'methods' is an instance
of ServiceLoader
+ * and its "instanceof" check should be very fast since ServiceLoader
is a final class.
+ */
+ if (!(methods instanceof ServiceLoader || methods instanceof Set<?>)) {
+ if (cachedMethods.contains(method)) {
+ return false;
+ }
+ }
+ return cachedMethods.add(method);
+ }
+
+ /**
+ * Returns {@code true} if this set is empty.
+ */
+ @Override
+ public synchronized boolean isEmpty() {
+ if (!cachedMethods.isEmpty()) {
+ return false;
+ }
+ while (methodIterator != null) {
+ if (transfer()) return false;
+ }
+ return true;
+ }
+
+ /**
+ * Returns the number of elements in this set.
+ */
+ @Override
+ public synchronized int size() {
+ while (methodIterator != null) {
+ transfer();
+ }
+ return cachedMethods.size();
+ }
+
+ /**
+ * Returns {@code true} if {@link #next(int)} can return an operation
method at the given index.
+ */
+ final synchronized boolean hasNext(final int index) {
+ if (index >= cachedMethods.size()) {
+ do if (methodIterator == null) {
+ return false;
+ } while (!transfer());
+ }
+ return true;
+ }
+
+ /**
+ * Returns the operation method at the given index. In case of index out
of bounds, this method throws a
+ * {@link NoSuchElementException} instead than an {@link
IndexOutOfBoundsException} because this method
+ * is designed for being invoked by {@link Iter#next()}.
+ */
+ final synchronized OperationMethod next(final int index) {
+ if (index >= cachedMethods.size()) {
+ do if (methodIterator == null) {
+ throw new NoSuchElementException();
+ } while (!transfer());
+ }
+ return cachedMethods.get(index);
+ }
+
+ /**
+ * Returns an iterator over the elements contained in this set.
+ */
+ @Override
+ public Iterator<OperationMethod> iterator() {
+ return new Iterator<OperationMethod>() {
+ /** Index of the next element to be returned. */
+ private int cursor;
+
+ @Override
+ public boolean hasNext() {
+ return OperationMethodSet.this.hasNext(cursor);
+ }
+
+ @Override
+ public OperationMethod next() {
+ return OperationMethodSet.this.next(cursor++);
+ }
+ };
+ }
+}
Propchange:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sis/branches/JDK8/core/sis-referencing/src/main/java/org/apache/sis/referencing/operation/transform/OperationMethodSet.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Added:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java?rev=1657973&view=auto
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
(added)
+++
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
[UTF-8] Fri Feb 6 21:48:57 2015
@@ -0,0 +1,167 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one or more
+ * contributor license agreements. See the NOTICE file distributed with
+ * this work for additional information regarding copyright ownership.
+ * The ASF licenses this file to You 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.
+ */
+package org.apache.sis.referencing.operation.transform;
+
+import java.util.Map;
+import java.util.Iterator;
+import java.util.Collections;
+import java.util.NoSuchElementException;
+import org.opengis.parameter.ParameterDescriptorGroup;
+import org.opengis.referencing.operation.Projection;
+import org.opengis.referencing.operation.ConicProjection;
+import org.opengis.referencing.operation.PlanarProjection;
+import org.opengis.referencing.operation.CylindricalProjection;
+import org.opengis.referencing.operation.OperationMethod;
+import org.apache.sis.referencing.operation.DefaultOperationMethod;
+import org.apache.sis.parameter.DefaultParameterDescriptorGroup;
+import org.apache.sis.internal.util.UnmodifiableArrayList;
+import org.apache.sis.test.DependsOnMethod;
+import org.apache.sis.test.DependsOn;
+import org.apache.sis.test.TestCase;
+import org.junit.Test;
+
+import static org.junit.Assert.*;
+
+
+/**
+ * Tests {@link OperationMethodSet}.
+ *
+ * @author Martin Desruisseaux (Geomatys)
+ * @since 0.6
+ * @version 0.6
+ * @module
+ */
+@DependsOn({
+ org.apache.sis.referencing.operation.DefaultOperationMethodTest.class,
+})
+public final strictfp class OperationMethodSetTest extends TestCase {
+ /**
+ * Creates a new two-dimensional operation method for an operation of the
given name.
+ *
+ * @param type The value to be returned by {@link
DefaultOperationMethod#getOperationType()}.
+ * @param method The operation name (example: "Mercator (variant A)").
+ * @return The operation method.
+ */
+ @SuppressWarnings("serial")
+ private static DefaultOperationMethod createMethod(final Class<? extends
Projection> type, final String method) {
+ Map<String,?> properties =
Collections.singletonMap(DefaultOperationMethod.NAME_KEY, method);
+ final ParameterDescriptorGroup parameters = new
DefaultParameterDescriptorGroup(properties, 1, 1);
+ /*
+ * Recycle the ParameterDescriptorGroup name for
DefaultOperationMethod.
+ * This save us one object creation, and is often the same name anyway.
+ */
+ properties = Collections.singletonMap(DefaultOperationMethod.NAME_KEY,
parameters.getName());
+ return new DefaultOperationMethod(properties, 2, 2, parameters) {
+ @Override public Class<? extends Projection> getOperationType() {
+ return type;
+ }
+ };
+ }
+
+ /**
+ * Creates an {@code OperationMethodSet} from the given methods, using an
iterable
+ * which will guarantee that the iteration is performed at most once.
+ *
+ * @param type The type of coordinate operation for which to retain
methods.
+ * @param methods The {@link DefaultMathTransformFactory#methods} used for
fetching the initial methods.
+ */
+ private static OperationMethodSet create(final Class<? extends Projection>
type, final DefaultOperationMethod... methods) {
+ @SuppressWarnings("serial")
+ final Iterable<DefaultOperationMethod> asList = new
UnmodifiableArrayList<DefaultOperationMethod>(methods) {
+ private boolean isIterationDone;
+
+ @Override
+ public Iterator<DefaultOperationMethod> iterator() {
+ assertFalse("Expected no more than one iteration.",
isIterationDone);
+ isIterationDone = true;
+ return super.iterator();
+ }
+ };
+ synchronized (asList) { // Needed for avoiding assertion error in
OperationMethodSet.
+ return new OperationMethodSet(type, asList);
+ }
+ }
+
+ /**
+ * Tests construction from an empty list.
+ */
+ @Test
+ public void testEmpty() {
+ assertEmpty(create(Projection.class));
+ }
+
+ /**
+ * Asserts that the given {@link OperationMethodSet} is empty.
+ */
+ private static void assertEmpty(final OperationMethodSet set) {
+ assertTrue ("isEmpty", set.isEmpty());
+ assertEquals("size", 0, set.size());
+ final Iterator<OperationMethod> iterator = set.iterator();
+ assertFalse(iterator.hasNext());
+ try {
+ iterator.next();
+ fail("Expected NoSuchElementException");
+ } catch (NoSuchElementException e) {
+ // This is the expected exception.
+ }
+ }
+
+ /**
+ * Tests a non-empty set.
+ */
+ @Test
+ @DependsOnMethod("testEmpty")
+ public void testMixedCases() {
+ final DefaultOperationMethod merA =
createMethod(CylindricalProjection.class, "Mercator (variant A)");
+ final DefaultOperationMethod merB =
createMethod(CylindricalProjection.class, "Mercator (variant B)");
+ final DefaultOperationMethod merC =
createMethod(CylindricalProjection.class, "Mercator (variant C)");
+ final DefaultOperationMethod dup =
createMethod(CylindricalProjection.class, "Mercator (variant B)");
+ final DefaultOperationMethod lamb =
createMethod(ConicProjection.class, "Lambert");
+ final DefaultOperationMethod[] methods = new DefaultOperationMethod[]
{merA, merB, merC, dup, lamb};
+ final OperationMethodSet mercators =
create(CylindricalProjection.class, methods);
+ final OperationMethodSet lambert = create(
ConicProjection.class, methods);
+ final OperationMethodSet all = create(
Projection.class, methods);
+ /*
+ * Mercator case.
+ * - Intentionally start the iteration without checking 'hasNext()'
- the iterator shall be robust to that.
+ * - Intentionally start an other iteration (indirectly) in the
middle of the first one.
+ */
+ final Iterator<OperationMethod> iterator = mercators.iterator();
+ assertSame(merA, iterator.next());
+ assertSame(merB, iterator.next());
+ assertArrayEquals("toArray", new DefaultOperationMethod[] {merA, merB,
merC}, mercators.toArray());
+ assertSame(merC, iterator.next());
+ assertFalse (iterator.hasNext());
+ assertFalse ("isEmpty", mercators.isEmpty());
+ assertEquals("size", 3, mercators.size());
+ /*
+ * Lambert case. Test twice since the two excecutions will take
different code paths.
+ */
+ assertEquals(Collections.singleton(lamb), lambert);
+ assertEquals(Collections.singleton(lamb), lambert);
+ /*
+ * Test filtering: the test should not contain any conic projection.
+ */
+ assertEmpty(create(PlanarProjection.class, methods));
+ /*
+ * Opportunist tests.
+ */
+ assertFalse(lambert.containsAll(all));
+ assertTrue(all.containsAll(lambert));
+ assertTrue(all.containsAll(mercators));
+ }
+}
Propchange:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
------------------------------------------------------------------------------
svn:eol-style = native
Propchange:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/referencing/operation/transform/OperationMethodSetTest.java
------------------------------------------------------------------------------
svn:mime-type = text/plain;charset=UTF-8
Modified:
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
URL:
http://svn.apache.org/viewvc/sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java?rev=1657973&r1=1657972&r2=1657973&view=diff
==============================================================================
---
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
[UTF-8] (original)
+++
sis/branches/JDK8/core/sis-referencing/src/test/java/org/apache/sis/test/suite/ReferencingTestSuite.java
[UTF-8] Fri Feb 6 21:48:57 2015
@@ -26,7 +26,7 @@ import org.junit.BeforeClass;
*
* @author Martin Desruisseaux (Geomatys)
* @since 0.3
- * @version 0.5
+ * @version 0.6
* @module
*/
@Suite.SuiteClasses({
@@ -79,6 +79,7 @@ import org.junit.BeforeClass;
org.apache.sis.referencing.operation.DefaultFormulaTest.class,
org.apache.sis.referencing.operation.DefaultOperationMethodTest.class,
+
org.apache.sis.referencing.operation.transform.OperationMethodSetTest.class,
org.apache.sis.internal.referencing.OperationMethodsTest.class,
org.apache.sis.referencing.datum.BursaWolfParametersTest.class,