sis-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From desruisse...@apache.org
Subject [sis] branch geoapi-4.0 updated: Consolidation of CRSChooser in situations where an error occurs during the construction of a CRS. Before this commit, the CRSChooser behavior in such case was confusion (e.g. filtering not working anymore).
Date Mon, 20 Apr 2020 16:18:53 GMT
This is an automated email from the ASF dual-hosted git repository.

desruisseaux pushed a commit to branch geoapi-4.0
in repository https://gitbox.apache.org/repos/asf/sis.git


The following commit(s) were added to refs/heads/geoapi-4.0 by this push:
     new b0cf8d5  Consolidation of CRSChooser in situations where an error occurs during the
construction of a CRS. Before this commit, the CRSChooser behavior in such case was confusion
(e.g. filtering not working anymore).
b0cf8d5 is described below

commit b0cf8d53cfd20866d60d4b2e044d1a4c2bdbd321
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Apr 20 18:17:11 2020 +0200

    Consolidation of CRSChooser in situations where an error occurs during the construction
of a CRS.
    Before this commit, the CRSChooser behavior in such case was confusion (e.g. filtering
not working anymore).
---
 .../apache/sis/gui/referencing/AuthorityCodes.java | 284 +++++++++++++--------
 .../org/apache/sis/gui/referencing/CRSChooser.java |   7 +-
 .../org/apache/sis/gui/referencing/CodeFilter.java |   8 +-
 .../gui/referencing/RecentReferenceSystems.java    |  58 ++---
 .../java/org/apache/sis/gui/referencing/Utils.java |  18 ++
 .../org/apache/sis/gui/referencing/WKTPane.java    |  30 ++-
 6 files changed, 240 insertions(+), 165 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
index ac6f0bc..9468350 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.Iterator;
 import java.util.IdentityHashMap;
 import java.util.List;
+import java.util.ListIterator;
 import java.util.Locale;
 import java.util.Map;
 import javafx.application.Platform;
@@ -36,23 +37,25 @@ import org.opengis.util.FactoryException;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.apache.sis.referencing.CRS;
+import org.apache.sis.util.iso.Types;
 import org.apache.sis.util.Exceptions;
+import org.apache.sis.util.logging.Logging;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.internal.util.StandardDateFormat;
 import org.apache.sis.internal.gui.BackgroundThreads;
-import org.apache.sis.internal.gui.ExceptionReporter;
-import org.apache.sis.internal.util.Constants;
+import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.Strings;
 
 
 /**
  * A list of authority codes (usually for CRS) which fetch code values in a background thread
- * and descriptions only when needed.
+ * and CRS names only when needed.
  *
  * @todo {@link org.apache.sis.referencing.factory.sql.EPSGDataAccess} internally uses a
{@link java.util.Map}
  *       from codes to descriptions. We could open an access to this map for a little bit
more efficiency.
- *       It will be necessary if we want to use {@link AuthorityCodes} for other kinds of
objects than CRS.
+ *       It will be necessary if we want to use {@link AuthorityCodes} for other kinds of
objects than CRS
+ *       (see {@link #type} field).
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @version 1.1
@@ -65,7 +68,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
     /**
      * Delay in nanoseconds before to refresh the list with new content.
      * Data will be transferred from background threads to JavaFX threads every time this
delay is elapsed.
-     * Delay value is a compromise between fast user experience and giving enough time for
allowing a few
+     * The delay value is a compromise between fast user experience and giving enough time
for doing a few
      * large data transfers instead than many small data transfers.
      */
     private static final long REFRESH_DELAY = StandardDateFormat.NANOS_PER_SECOND / 10;
@@ -87,12 +90,13 @@ final class AuthorityCodes extends ObservableListBase<Code>
      * The authority codes obtained from the factory. The list elements are provided by a
background thread.
      * Elements are initially {@link String} instances and can be replaced later by {@link
Code} instances.
      */
-    private Object[] codes;
+    private final List<Object> codes;
 
     /**
      * Count of the number of {@linkplain #codes} for which we completed the {@link Code#name}
information.
      * This is used for notifying the {@linkplain #owner} when we do not expect more information
to be loaded.
-     * This notification is only indicative and may not be fully accurate. Effect should
be only visual.
+     * This notification is only indicative and may not be fully accurate. Its effect should
be only visual
+     * (removing the hour glass icon).
      */
     private int describedCount;
 
@@ -102,36 +106,55 @@ final class AuthorityCodes extends ObservableListBase<Code>
     final Locale locale;
 
     /**
+     * The factory to use for creating coordinate reference systems,
+     * or {@code null} if not yet determined.
+     *
+     * @see #getFactory()
+     */
+    private CRSAuthorityFactory factory;
+
+    /**
      * The task where to send requests for CRS descriptions (never {@code null}).
+     * The task is not necessarily running; it may have been created and not yet scheduled,
+     * in which case the task is waiting in {@link Task.State#READY} state for work to arrive.
      */
     private Loader loader;
 
     /**
-     * Non-null if an error occurred while fetching CRS codes.
+     * {@code true} if an error occurred. This is used for reporting only one error
+     * for avoiding to flood the logger.
      *
-     * @todo Provide a button for showing this error in an {@link ExceptionReporter}.
+     * @see #errorOccurred(Throwable)
      */
-    private Throwable error;
+    private volatile boolean hasError;
 
     /**
      * Creates a new deferred list and starts a background process for loading CRS codes.
+     * If the given factory is {@code null}, then a
+     * {@linkplain org.apache.sis.referencing.CRS#getAuthorityFactory(String) default factory}
+     * capable to handle at least some EPSG codes will be used.
      *
      * @param  factory  the authority factory, or {@code null} for default factory.
      * @param  locale   the preferred locale of CRS descriptions.
      */
     AuthorityCodes(final CRSAuthorityFactory factory, final Locale locale) {
-        this.locale = locale;
-        codes  = new Object[0];
-        loader = new Loader(factory);
-        BackgroundThreads.execute(loader);
+        this.locale  = locale;
+        this.factory = factory;
+        this.codes   = new ArrayList<>();
+        this.loader  = new Loader();
+        loader.start();
     }
 
     /**
-     * Returns the authority factory. If no explicit factory has been given at construction
time,
-     * the {@linkplain CRS#getAuthorityFactory(String) Apache SIS default factory} is returned.
+     * Returns the authority factory. This method may be invoked from any thread.
+     * The factory is not fetched at construction time for giving {@link Loader}
+     * a chance to fetch it in a background thread.
      */
-    final CRSAuthorityFactory getFactory() throws FactoryException {
-        return loader.getFactory();
+    final synchronized CRSAuthorityFactory getFactory() throws FactoryException {
+        if (factory == null) {
+            factory = Utils.getDefaultFactory();
+        }
+        return factory;
     }
 
     /**
@@ -140,7 +163,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
      */
     @Override
     public int size() {
-        return codes.length;
+        return codes.size();
     }
 
     /**
@@ -148,25 +171,24 @@ final class AuthorityCodes extends ObservableListBase<Code>
      */
     @Override
     public Code get(final int index) {
-        final Object value = codes[index];
+        final Object value = codes.get(index);
         if (value instanceof Code) {
             return (Code) value;
         }
         // Wraps the String only when first needed.
         final Code c = new Code((String) value);
-        codes[index] = c;
+        codes.set(index, c);
         return c;
     }
 
     /**
-     * Adds a single code. This method should never be invoked except of an error occurred
+     * Adds a single code. This method should never be invoked except if an error occurred
      * while loading codes, in which case we add a single pseudo-code with error message.
      */
     @Override
     public boolean add(final Code code) {
-        final int i = codes.length;
-        codes = Arrays.copyOf(codes, i + 1);
-        codes[i] = code;
+        final int i = codes.size();
+        codes.add(code);
         beginChange();
         nextAdd(i, i+1);
         endChange();
@@ -176,7 +198,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
     /**
      * Invoked when the name or description of an authority code is requested.
      * If the name is not available, then this method sends to the background thread a
-     * request for fetching that name and update this property when name become known.
+     * request for fetching that name and update cell property when name become known.
      */
     @Override
     public ObservableValue<String> call(final TableColumn.CellDataFeatures<Code,String>
cell) {
@@ -185,8 +207,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
 
     /**
      * Returns the name (or description) for the given code.
-     * If the name is not available, then this method sends to the background thread a
-     * request for fetching that name and update this property when name become known.
+     * If the name is not available, then this method sends to the background thread a request
+     * for fetching that name and will update the returned property when the name become
known.
      */
     final ReadOnlyStringWrapper getName(final Code code) {
         final ReadOnlyStringWrapper p = code.name();
@@ -202,32 +224,36 @@ final class AuthorityCodes extends ObservableListBase<Code>
      * This method is invoked after the background thread has loaded new codes,
      * and/or after that thread has fetched names (descriptions) of some codes.
      * We combine those two tasks in a single method in order to send a single event.
-     *
-     * @param newCodes  new codes as {@link String} instances, or {@code null} if none.
-     * @param updated   {@link Code} instances to update with new names, or {@code null}
if none.
+     * This method must be invoked in JavaFX thread.
      */
-    private void update(final Object[] newCodes, final Map<Code,String> updated) {
-        final int s = codes.length;
-        int n = s;
-        if (newCodes != null) {
-            codes = Arrays.copyOf(codes, n += newCodes.length);
-            System.arraycopy(newCodes, 0, codes, s, newCodes.length);
+    private void update(final PartialResult result) {
+        assert Platform.isFxApplicationThread();
+        final int s = codes.size();
+        if (result.codes != null) {
+            codes.addAll(Arrays.asList(result.codes));
         }
         beginChange();
-        if (updated != null) {
-            for (int i=0; i<s; i++) {                           // Update names first
for having increasing indices.
-                final Object value = codes[i];
-                final String name = updated.remove(value);
+        nextAdd(s, codes.size());
+        if (result.names != null) {
+            final ListIterator<Object> it = codes.listIterator();
+            while (it.hasNext()) {
+                final Object value = it.next();
+                final String name = result.names.remove(value);
                 if (name != null) {
-                    ((Code) value).name().set(name);            // The name needs to be set
in JavaFX thread.
-                    describedCount++;
-                    nextUpdate(i);
+                    final int i = it.previousIndex();
+                    if (name.isEmpty()) {
+                        it.remove();                        // Remove code that we can not
resolve.
+                        nextRemove(i, (Code) value);        // ClassCastException should
never happen here.
+                    } else {
+                        ((Code) value).name().set(name);    // ClassCastException should
never happen here.
+                        describedCount++;
+                        nextUpdate(i);
+                    }
                 }
             }
         }
-        nextAdd(s, n);
         endChange();
-        if (describedCount >= n) {
+        if (describedCount >= codes.size()) {
             removeHourglass();
         }
     }
@@ -236,7 +262,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
      * Removes the hourglass icon which was shown in the table during initial data loading
phase.
      * Removing this icon restores the JavaFX default behavior, which is to show "no data"
when the
      * list is empty. We want this default behavior when we think that there is no more data
to load.
-     * This is especially important when the user apply a filter which produces an empty
result.
+     * This is especially important when the user applies a filter which produces an empty
result.
      * Since the effect is only visual, its okay if the criterion for invoking this method
is approximate.
      */
     private void removeHourglass() {
@@ -247,19 +273,37 @@ final class AuthorityCodes extends ObservableListBase<Code>
     }
 
     /**
-     * Loads a {@link AuthorityCodes} codes in background thread. This background thread
may send tasks
-     * to be executed in JavaFX thread before the final result. The final result contains
only the codes
-     * that have not been processed by above-cited tasks or the codes for which names need
to be updated
-     * (see {@link #call()} for more information).
+     * The result of fetching authority codes and/or fetching CRS names in a background thread.
      */
-    private final class Loader extends Task<Object> {
+    private static final class PartialResult {
+        /**
+         * New CRS authority codes, or {@code null} if none.
+         */
+        final Object[] codes;
+
         /**
-         * The factory to use for creating coordinate reference systems,
-         * or {@code null} if not yet determined.
+         * Names for some CRS codes as a modifiable map, or {@code null} if none.
+         * Empty values mean that the code should be removed (because it has an error).
          */
-        private CRSAuthorityFactory factory;
+        final Map<Code,String> names;
 
         /**
+         * Creates a new partial result.
+         */
+        PartialResult(final Object[] codes, final Map<Code,String> names) {
+            this.codes = codes;
+            this.names = names;
+        }
+    }
+
+    /**
+     * Loads CRS authority codes in background thread. The background thread may send tasks
to be executed
+     * in JavaFX thread before the final result. The final result returned by {@link #getValue()}
contains
+     * only codes that have not been fetched by previous {@code Loader} task executions,
or the codes for
+     * which names need to be updated (see {@link #call()} for more information).
+     */
+    private final class Loader extends Task<PartialResult> {
+        /**
          * The items for which {@link Code#name} has been requested.
          * Completing those items have priority over completing {@link AuthorityCodes} because
          * those completion requests should happen only for cells that are currently visible.
@@ -275,13 +319,21 @@ final class AuthorityCodes extends ObservableListBase<Code>
         private final boolean loadCodes;
 
         /**
-         * Creates a new loader using the given factory. If the given factory is null, then
the
-         * {@linkplain CRS#getAuthorityFactory(String) Apache SIS default factory} will be
used.
+         * Wether this task has been scheduled for execution or is already executing.
+         * This flag shall be read and updated in JavaFX thread only. We can not rely
+         * on {@link #isRunning()} because that method does not return {@code true}
+         * immediately after {@link BackgroundThreads#execute(Runnable)} invocation.
+         *
+         * @see #start()
          */
-        Loader(final CRSAuthorityFactory factory) {
-            this.factory = factory;
-            toDescribe   = new ArrayList<>();
-            loadCodes    = true;
+        private boolean isRunning;
+
+        /**
+         * Creates a new loader.
+         */
+        Loader() {
+            toDescribe = new ArrayList<>();
+            loadCodes  = true;
         }
 
         /**
@@ -289,43 +341,50 @@ final class AuthorityCodes extends ObservableListBase<Code>
          * for loading names (descriptions) for authority codes listed in {@link #toDescribe}.
          */
         private Loader(final Loader previous) {
-            factory    = previous.factory;
             toDescribe = previous.toDescribe;
             loadCodes  = false;
         }
 
         /**
-         * Returns the authority factory. This method is normally invoked from the background
thread,
-         * but we nevertheless synchronize it in case {@link AuthorityCodes#getFactory()}
is invoked
-         * concurrently.
+         * Schedule for execution in a background thread.
+         * This method shall be invoked in JavaFX thread.
          */
-        final synchronized CRSAuthorityFactory getFactory() throws FactoryException {
-            if (factory == null) {
-                factory = CRS.getAuthorityFactory(Constants.EPSG);
-            }
-            return factory;
+        final void start() {
+            isRunning = true;
+            BackgroundThreads.execute(this);
         }
 
         /**
          * Sends to this background thread a request for fetching the name (description)
of given code.
          * The {@link AuthorityCodes} list will receive an update event after the name has
been fetched.
-         * This method is invoked from JavaFX thread.
+         * This method must be invoked from JavaFX thread.
+         *
+         * @param  code  the CRS authority code for which to fetch the name in background
thread.
          */
         final void requestName(final Code code) {
+            assert Platform.isFxApplicationThread();
             synchronized (toDescribe) {
                 toDescribe.add(code);
             }
-            if (!isRunning()) {                         // Include "scheduled" state.
-                BackgroundThreads.execute(this);
+            /*
+             * This task may be created and ready but not yet started. It happens if `scheduleNewLoader()`
+             * found no code to process in the `toDescribe` list at the time that method
has been invoked.
+             */
+            if (!isRunning) {
+                start();
             }
         }
 
         /**
          * Fetches the names of all objects in the {@link #toDescribe} array and clears that
array.
-         * The names are returned as a map with {@link Code} as keys and names (descriptions)
as values.
-         * This method is invoked from background thread and returned value will be consumed
in JavaFX thread.
+         * The names are returned as a map with {@link Code} as keys and names (or descriptions)
as values.
+         * This method is invoked from a background thread and the returned value will be
consumed in JavaFX thread.
+         * Some entries in the returned map be empty strings if the corresponding code should
be removed.
+         *
+         * @param  factory  value of {@link #getFactory()}.
+         * @return the names of CRS authority codes submitted to {@link #requestName(Code)},
or {@code null} if none.
          */
-        private Map<Code,String> processNameRequests() throws FactoryException {
+        private Map<Code,String> processNameRequests(final CRSAuthorityFactory factory)
{
             final Code[] snapshot;
             synchronized (toDescribe) {
                 final int size = toDescribe.size();
@@ -333,11 +392,19 @@ final class AuthorityCodes extends ObservableListBase<Code>
                 snapshot = toDescribe.toArray(new Code[size]);
                 toDescribe.clear();
             }
-            final CRSAuthorityFactory factory = getFactory();
             final Map<Code,String> updated = new IdentityHashMap<>(snapshot.length);
             for (final Code code : snapshot) {
-                // Do not update code in this thread; it will be updated in JavaFX thread.
-                updated.put(code, factory.getDescriptionText(code.code).toString(locale));
+                String text;
+                try {
+                    text = Strings.trimOrNull(Types.toString(factory.getDescriptionText(code.code),
locale));
+                    if (text == null) {
+                        text = Vocabulary.getResources(locale).getString(Vocabulary.Keys.Unnamed);
+                    }
+                } catch (FactoryException e) {
+                    errorOccurred(e);
+                    text = "";              // Tells `AuthorityCodes.update(PartialResult)`
to remove this code.
+                }
+                updated.put(code, text);    // Do not update code in this thread; it will
be updated in JavaFX thread.
             }
             return updated;
         }
@@ -345,12 +412,12 @@ final class AuthorityCodes extends ObservableListBase<Code>
         /**
          * Invoked in background thread for reading authority codes. Intermediate results
are sent
          * to the JavaFX thread every {@value #REFRESH_DELAY} nanoseconds. Requests for code
names
-         * are also handled in priority since they are typically for visible cells.
+         * are also handled in priority since they are typically required for visible cells.
          *
-         * @return one of the followings:
+         * @return one or both of the followings:
          *   <ul>
-         *     <li>A {@code List<String>} which contains the remaining codes
that need to be
-         *         sent to {@link AuthorityCodes} list.</li>
+         *     <li>An array of {@code String}s which contains the remaining codes that
need
+         *         to be sent to {@link AuthorityCodes} list.</li>
          *     <li>A {@code Map<Code,String>} which contains the codes for which
the names
          *         or descriptions have been updated.</li>
          *   </ul>
@@ -358,7 +425,7 @@ final class AuthorityCodes extends ObservableListBase<Code>
          * @throws Exception if an error occurred while fetching the codes or the names/descriptions.
          */
         @Override
-        protected Object call() throws Exception {
+        protected PartialResult call() throws Exception {
             long lastTime = System.nanoTime();
             List<String> codes = Collections.emptyList();
             final CRSAuthorityFactory factory = getFactory();
@@ -369,10 +436,9 @@ final class AuthorityCodes extends ObservableListBase<Code>
                     while (it.hasNext()) {
                         codes.add(it.next());
                         if (System.nanoTime() - lastTime > REFRESH_DELAY) {
-                            final Object[] newCodes = codes.toArray();                //
Snapshot of current content.
+                            final PartialResult p = new PartialResult(codes.toArray(), processNameRequests(factory));
                             codes.clear();
-                            final Map<Code,String> updated = processNameRequests();
  // Must be outside lambda expression.
-                            Platform.runLater(() -> update(newCodes, updated));
+                            Platform.runLater(() -> update(p));
                             lastTime = System.nanoTime();
                         }
                     }
@@ -385,12 +451,12 @@ final class AuthorityCodes extends ObservableListBase<Code>
                  */
                 if (codes.isEmpty()) {
                     Thread.sleep(REFRESH_DELAY / StandardDateFormat.NANOS_PER_MILLISECOND);
-                    return processNameRequests();
+                    return new PartialResult(null, processNameRequests(factory));
                 }
             } catch (BackingStoreException e) {
                 throw e.unwrapOrRethrow(Exception.class);
             }
-            return codes;
+            return new PartialResult(codes.toArray(), null);
         }
 
         /**
@@ -401,19 +467,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
         @Override
         @SuppressWarnings("unchecked")
         protected void succeeded() {
-            Object[] newCodes = null;
-            Map<Code,String> updated = null;
-            final Object result = getValue();
-            if (result instanceof List<?>){
-                final List<?> codes = (List<?>) result;
-                if (!codes.isEmpty()) {
-                    newCodes = codes.toArray();
-                }
-            } else {
-                updated = (Map<Code,String>) result;
-            }
-            update(newCodes, updated);
-            scheduleNewLoader();
+            update(getValue());
+            prepareNewLoader();
         }
 
         /**
@@ -424,7 +479,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
         @Override
         protected void failed() {
             final Throwable e = getException();
-            if (error == null) {
+            errorOccurred(e);
+            if (loadCodes) {
                 final Code code = new Code(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Errors));
                 String message = Exceptions.getLocalizedMessage(e, locale);
                 if (message == null) {
@@ -433,9 +489,8 @@ final class AuthorityCodes extends ObservableListBase<Code>
                 code.name().set(message);
                 add(code);
             }
-            error = e;
             removeHourglass();
-            scheduleNewLoader();
+            prepareNewLoader();
         }
 
         /**
@@ -443,15 +498,28 @@ final class AuthorityCodes extends ObservableListBase<Code>
          * between the end of {@link #call()} execution and the start of the {@link #succeeded()}
or
          * {@link #failed()} execution, starts the new task immediately.
          */
-        private void scheduleNewLoader() {
+        private void prepareNewLoader() {
+            assert Platform.isFxApplicationThread();
+            isRunning = false;
             loader = new Loader(this);
             final boolean isEmpty;
             synchronized (toDescribe) {
                 isEmpty = toDescribe.isEmpty();
             }
             if (!isEmpty) {
-                BackgroundThreads.execute(loader);
+                loader.start();
             }
         }
     }
+
+    /**
+     * Invoked when an error occurred. This method may be invoked from any thread.
+     * Current implementation logs the first error.
+     */
+    private void errorOccurred(final Throwable e) {
+        if (!hasError) {
+            hasError = true;    // Not a big problem if we have race condition; error will
just be logged twice.
+            Logging.unexpectedException(Logging.getLogger(Modules.APPLICATION), AuthorityCodes.class,
"get", e);
+        }
+    }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
index 28a4eab..f2c427d 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CRSChooser.java
@@ -69,7 +69,6 @@ import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.Exceptions;
-import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
 
 
@@ -144,9 +143,11 @@ public class CRSChooser extends Dialog<CoordinateReferenceSystem>
{
 
     /**
      * Creates a chooser proposing all coordinate reference systems from the given factory.
+     * If the given factory is {@code null}, then a
+     * {@linkplain org.apache.sis.referencing.CRS#getAuthorityFactory(String) default factory}
+     * capable to handle at least some EPSG codes will be used.
      *
-     * @param  factory         the factory to use for creating coordinate reference systems,
or {@code null}
-     *                         for the {@linkplain CRS#getAuthorityFactory(String) Apache
SIS default factory}.
+     * @param  factory         the factory to use for creating coordinate reference systems,
or {@code null} for default.
      * @param  areaOfInterest  geographic area for which to choose a CRS, or {@code null}
if no restriction.
      */
     public CRSChooser(final CRSAuthorityFactory factory, final Envelope areaOfInterest) {
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
index 59dcfab..103c0c7 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/CodeFilter.java
@@ -95,13 +95,11 @@ final class CodeFilter implements Predicate<Code> {
      */
     @Override
     public boolean test(final Code code) {
+        final String id = code.code.toLowerCase(allCodes.locale);
         String name = allCodes.getName(code).getValue();
-        if (name == null) {
-            return false;
-        }
-        name = name.toLowerCase(allCodes.locale);
+        name = (name != null) ? name.toLowerCase(allCodes.locale) : "";
         for (final String token : tokens) {
-            if (!name.contains(token)) {
+            if (!name.contains(token) && !id.equals(token)) {
                 return false;
             }
         }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
index 9670f14..35d9869 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/RecentReferenceSystems.java
@@ -34,10 +34,7 @@ import org.opengis.referencing.ReferenceSystem;
 import org.opengis.referencing.IdentifiedObject;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.opengis.metadata.extent.GeographicBoundingBox;
-import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.geometry.ImmutableEnvelope;
-import org.apache.sis.referencing.CRS;
 import org.apache.sis.referencing.IdentifiedObjects;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
@@ -74,27 +71,21 @@ import org.apache.sis.internal.util.Strings;
  */
 public class RecentReferenceSystems {
     /**
-     * The authority of the {@link #factory} (for example "EPSG"),
-     * or {@code null} for all authorities known to SIS.
-     */
-    private static final String AUTHORITY = null;
-
-    /**
      * Number of reference systems to always show before all other reference systems.
      * They are the native of preferred reference system for the visualized data.
      */
-    private static final int NUM_CORE_SYSTEMS = 1;
+    private static final int NUM_CORE_ITEMS = 1;
 
     /**
      * Number of reference systems to shown in {@link ChoiceBox} or {@link MenuItem}s.
-     * The {@value #NUM_CORE_SYSTEMS} core systems are included but not {@link #OTHER}.
+     * The {@value #NUM_CORE_ITEMS} core systems are included but not {@link #OTHER}.
      */
-    private static final int NUM_SHOWN_SYSTEMS = 9;
+    private static final int NUM_SHOWN_ITEMS = 9;
 
     /**
      * Number of reference systems to keep at the end of the list.
      */
-    private static final int NUM_OTHER_SYSTEMS = 1;
+    private static final int NUM_OTHER_ITEMS = 1;
 
     /**
      * A pseudo-reference system for the "Other…" choice. We use a null value because {@link
ChoiceBox}
@@ -104,8 +95,7 @@ public class RecentReferenceSystems {
 
     /**
      * The factory to use for creating a Coordinate Reference System from an authority code.
-     * If {@code null}, then the {@linkplain CRS#getAuthorityFactory(String) default factory}
-     * will be fetched when first needed.
+     * If {@code null}, then a default factory will be fetched when first needed.
      */
     private volatile CRSAuthorityFactory factory;
 
@@ -189,7 +179,8 @@ public class RecentReferenceSystems {
     private boolean isAdjusting;
 
     /**
-     * Creates a builder which will use the {@linkplain CRS#getAuthorityFactory(String) default
authority factory}.
+     * Creates a builder which will use a default authority factory.
+     * The factory will be capable to handle at least some EPSG codes.
      */
     public RecentReferenceSystems() {
         systemsOrCodes       = new ArrayList<>();
@@ -208,7 +199,7 @@ public class RecentReferenceSystems {
      *
      * @param  factory  the factory to use for building CRS from authority codes.
      *
-     * @see CRS#getAuthorityFactory(String)
+     * @see org.apache.sis.referencing.CRS#getAuthorityFactory(String)
      */
     public RecentReferenceSystems(final CRSAuthorityFactory factory) {
         this();
@@ -358,7 +349,7 @@ public class RecentReferenceSystems {
                      */
                     if (!noFactoryFound) {
                         if (factory == null) {
-                            factory = CRS.getAuthorityFactory(AUTHORITY);
+                            factory = Utils.getDefaultFactory();
                         }
                         systemsOrCodes.set(i, factory.createCoordinateReferenceSystem((String)
item));
                     } else {
@@ -376,7 +367,7 @@ public class RecentReferenceSystems {
                         if (factory instanceof GeodeticAuthorityFactory) {
                             finder = ((GeodeticAuthorityFactory) factory).newIdentifiedObjectFinder();
                         } else {
-                            finder = IdentifiedObjects.newFinder(AUTHORITY);
+                            finder = IdentifiedObjects.newFinder(null);
                         }
                         finder.setIgnoringAxes(true);
                     }
@@ -420,17 +411,14 @@ public class RecentReferenceSystems {
              * because they would become valid later if the area of interest changes.
              */
             final int n = systemsOrCodes.size();
-            systems = new ArrayList<>(Math.min(NUM_SHOWN_SYSTEMS, n) + NUM_OTHER_SYSTEMS);
+            systems = new ArrayList<>(Math.min(NUM_SHOWN_ITEMS, n) + NUM_OTHER_ITEMS);
             for (int i=0; i<n; i++) {
                 final ReferenceSystem system = (ReferenceSystem) systemsOrCodes.get(i);
-                if (i >= NUM_CORE_SYSTEMS && domain != null) {
-                    final GeographicBoundingBox bbox = Extents.getGeographicBoundingBox(system.getDomainOfValidity());
-                    if (bbox != null && !domain.intersects(new ImmutableEnvelope(bbox)))
{
-                        continue;
-                    }
+                if (i >= NUM_CORE_ITEMS && !Utils.intersects(domain, system.getDomainOfValidity()))
{
+                    continue;
                 }
                 systems.add(system);
-                if (systems.size() >= NUM_SHOWN_SYSTEMS) break;
+                if (systems.size() >= NUM_SHOWN_ITEMS) break;
             }
             systems.add(OTHER);
             isModified   = false;
@@ -466,7 +454,7 @@ public class RecentReferenceSystems {
             referenceSystems = FXCollections.observableArrayList();
         }
         synchronized (systemsOrCodes) {
-            systemsOrCodes.addAll(Math.min(systemsOrCodes.size(), NUM_CORE_SYSTEMS), referenceSystems);
+            systemsOrCodes.addAll(Math.min(systemsOrCodes.size(), NUM_CORE_ITEMS), referenceSystems);
             // Duplicated values will be filtered by the background task below.
             isModified = true;
             final ImmutableEnvelope domain = geographicAOI;
@@ -566,11 +554,11 @@ public class RecentReferenceSystems {
                 } else {
                     final ObservableList<ReferenceSystem> items = referenceSystems;
                     final ComparisonMode mode = duplicationCriterion.get();
-                    final int count = items.size() - NUM_OTHER_SYSTEMS;
+                    final int count = items.size() - NUM_OTHER_ITEMS;
                     boolean found = false;
                     for (int i=0; i<count; i++) {
                         if (Utilities.deepEquals(newValue, items.get(i), mode)) {
-                            if (i >= NUM_CORE_SYSTEMS) {
+                            if (i >= NUM_CORE_ITEMS) {
                                 items.set(i, newValue);
                             }
                             found = true;
@@ -578,10 +566,10 @@ public class RecentReferenceSystems {
                         }
                     }
                     if (!found) {
-                        if (count >= NUM_SHOWN_SYSTEMS) {
+                        if (count >= NUM_SHOWN_ITEMS) {
                             items.remove(count - 1);        // Remove the last item before
`OTHER`.
                         }
-                        items.add(Math.min(count, NUM_CORE_SYSTEMS), newValue);
+                        items.add(Math.min(count, NUM_CORE_ITEMS), newValue);
                     }
                 }
                 /*
@@ -603,19 +591,19 @@ public class RecentReferenceSystems {
                  * to confuse the list.
                  */
                 final ObservableList<ReferenceSystem> items = referenceSystems;
-                final int count = items.size() - NUM_OTHER_SYSTEMS;
-                for (int i=Math.min(count, NUM_CORE_SYSTEMS + 1); --i >= 0;) {
+                final int count = items.size() - NUM_OTHER_ITEMS;
+                for (int i=Math.min(count, NUM_CORE_ITEMS + 1); --i >= 0;) {
                     if (items.get(i) == newValue) {
                         return;
                     }
                 }
-                for (int i=count; --i >= NUM_CORE_SYSTEMS;) {
+                for (int i=count; --i >= NUM_CORE_ITEMS;) {
                     if (items.get(i) == newValue) {
                         items.remove(i);
                         break;
                     }
                 }
-                items.add(Math.max(0, Math.min(count, NUM_CORE_SYSTEMS)), newValue);
+                items.add(Math.max(0, Math.min(count, NUM_CORE_ITEMS)), newValue);
             }
         }
     }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
index ba451d4..06b547f 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Utils.java
@@ -17,14 +17,18 @@
 package org.apache.sis.gui.referencing;
 
 import org.opengis.geometry.Envelope;
+import org.opengis.util.FactoryException;
 import org.opengis.metadata.extent.Extent;
 import org.opengis.metadata.extent.GeographicBoundingBox;
+import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.operation.TransformException;
 import org.apache.sis.metadata.iso.extent.DefaultGeographicBoundingBox;
 import org.apache.sis.metadata.iso.extent.Extents;
 import org.apache.sis.geometry.ImmutableEnvelope;
 import org.apache.sis.internal.system.Modules;
+import org.apache.sis.internal.util.Constants;
 import org.apache.sis.util.logging.Logging;
+import org.apache.sis.referencing.CRS;
 
 
 /**
@@ -43,6 +47,20 @@ final class Utils {
     }
 
     /**
+     * Returns the default authority factory. Current implementation uses only the EPSG factory
for avoiding
+     * problems with "AUTO" factory (which requires parameters) and PROJ factory (which requires
native code).
+     * We lost the "CRS" factory, but it does not provide interesting new CRS compared to
EPSG factory.
+     * It provides CRS with different axis order such as "CRS:84", but widgets in this package
ignore axis order.
+     *
+     * <p>If a future version uses more than one authority factory, note that it would
have the side effect
+     * of making authority namespaces visible in the {@link CRSChooser} "Code" column, requiring
more space.
+     * For example "4326" would become "EPSG:4326". We may need to revisit the widget layout
in such case.</p>
+     */
+    static CRSAuthorityFactory getDefaultFactory() throws FactoryException {
+        return CRS.getAuthorityFactory(Constants.EPSG);
+    }
+
+    /**
      * Converts an arbitrary envelope to an envelope with (longitude, latitude) axis order
in degrees.
      * The datum is unspecified. This is used for approximate comparisons of geographic area.
      */
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
index 384f136..f5ddb5b 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/WKTPane.java
@@ -145,23 +145,34 @@ final class WKTPane extends StringConverter<Convention> implements
ChangeListene
     @Override
     public void changed(ObservableValue<? extends Convention> observable, Convention
oldValue, Convention newValue) {
         format.setConvention(newValue);
-        refresh();
+        if (crs != null) {
+            text.setText(format.format(crs));
+        }
     }
 
     /**
      * Sets the CRS to show in this pane. The CRS is constructed in a background thread.
+     * The execution will usually be very quick because {@link CRSChooser} already started
+     * a background thread for fetching the selected CRS, and WKT formatting is fast.
      */
     final void setContent(final AuthorityCodes source, final String code) {
         text.setDisable(true);
         BackgroundThreads.execute(new Task<CoordinateReferenceSystem>() {
+            /** The WKT text formatted in background thread. */
+            private String wkt;
+
             /** Invoked in background thread for fetching the CRS from an authority code.
*/
             @Override protected CoordinateReferenceSystem call() throws FactoryException
{
-                return source.getFactory().createCoordinateReferenceSystem(code);
+                final CoordinateReferenceSystem crs = source.getFactory().createCoordinateReferenceSystem(code);
+                if (crs != null) {
+                    wkt = format.format(crs);
+                }
+                return crs;
             }
 
             /** Invoked in JavaFX thread on success. */
             @Override protected void succeeded() {
-                setContent(getValue());
+                setContent(getValue(), wkt);
             }
 
             /** Invoked in JavaFX thread on cancellation. */
@@ -181,21 +192,12 @@ final class WKTPane extends StringConverter<Convention> implements
ChangeListene
     /**
      * Sets the content to the given coordinate reference system.
      */
-    private void setContent(final CoordinateReferenceSystem newCRS) {
+    private void setContent(final CoordinateReferenceSystem newCRS, final String wkt) {
         text.setEditable(false);     // TODO: make editable if we allow WKT parsing in a
future version.
         text.setDisable(false);
         if (newCRS != crs) {
             crs = newCRS;
-            refresh();
-        }
-    }
-
-    /**
-     * Rewrites the WKT using current conventions.
-     */
-    private void refresh() {
-        if (crs != null) {
-            text.setText(format.format(crs));
+            text.setText(wkt);
         }
     }
 }


Mime
View raw message