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: CRSChooser builds its list of CRS in a background thread. Filtering delegated to JavaFX FilteredList class.
Date Mon, 11 Nov 2019 16:39:08 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 6b11704  CRSChooser builds its list of CRS in a background thread. Filtering delegated
to JavaFX FilteredList class.
6b11704 is described below

commit 6b117040a9289506f361c47cf84576a49e57b48a
Author: Martin Desruisseaux <martin.desruisseaux@geomatys.com>
AuthorDate: Mon Nov 11 17:38:09 2019 +0100

    CRSChooser builds its list of CRS in a background thread.
    Filtering delegated to JavaFX FilteredList class.
---
 .../org/apache/sis/gui/dataset/FeatureTable.java   |   2 +-
 .../apache/sis/gui/referencing/AuthorityCodes.java | 389 +++++++++++++++++++++
 .../org/apache/sis/gui/referencing/CRSChooser.java | 174 ++++-----
 .../java/org/apache/sis/gui/referencing/Code.java  |  92 +++--
 .../org/apache/sis/internal/gui/Resources.java     |   5 +
 .../apache/sis/internal/gui/Resources.properties   |   1 +
 .../sis/internal/gui/Resources_fr.properties       |   1 +
 .../java/org/apache/sis/internal/gui/Styles.java   |   5 +
 .../referencing/factory/sql/EPSGDataAccess.java    |  13 +-
 .../sis/referencing/factory/sql/package-info.java  |   2 +-
 10 files changed, 547 insertions(+), 137 deletions(-)

diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
index e55ca3c..1263ed1 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/FeatureTable.java
@@ -112,13 +112,13 @@ public class FeatureTable extends TableView<Feature> {
      * Creates an initially empty table.
      */
     public FeatureTable() {
+        super(new FeatureList());
         textLocale       = Locale.getDefault(Locale.Category.DISPLAY);
         dataLocale       = Locale.getDefault(Locale.Category.FORMAT);
         featuresProperty = new SimpleObjectProperty<>(this, "features");
         featuresProperty.addListener(this::startFeaturesLoading);
         setColumnResizePolicy(CONSTRAINED_RESIZE_POLICY);
         setTableMenuButtonVisible(true);
-        setItems(new FeatureList());
     }
 
     /**
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
new file mode 100644
index 0000000..60e6e8d
--- /dev/null
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/AuthorityCodes.java
@@ -0,0 +1,389 @@
+/*
+ * 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.gui.referencing;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.Iterator;
+import java.util.IdentityHashMap;
+import java.util.List;
+import java.util.Locale;
+import java.util.Map;
+import javafx.application.Platform;
+import javafx.beans.property.ReadOnlyStringWrapper;
+import javafx.beans.value.ObservableValue;
+import javafx.collections.ObservableListBase;
+import javafx.scene.control.TableColumn;
+import javafx.concurrent.Task;
+import javafx.util.Callback;
+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.Exceptions;
+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.util.Constants;
+
+
+/**
+ * A list of authority codes (usually for CRS) which fetch code values in a background thread
+ * and descriptions 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.
+ *
+ * @author  Martin Desruisseaux (Geomatys)
+ * @version 1.1
+ * @since   1.1
+ * @module
+ */
+final class AuthorityCodes extends ObservableListBase<Code>
+        implements Callback<TableColumn.CellDataFeatures<Code,String>, ObservableValue<String>>
+{
+    /**
+     * 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
+     * large data transfers instead than many small data transfers.
+     */
+    private static final long REFRESH_DELAY = StandardDateFormat.NANOS_PER_SECOND / 10;
+
+    /**
+     * The type of object for which we want authority codes. Fixed to {@link CoordinateReferenceSystem}
for now,
+     * but could be made configurable in a future version. Making this field configurable
would require resolving
+     * the "todo" documented in class javadoc.
+     */
+    private static final Class<? extends IdentifiedObject> type = CoordinateReferenceSystem.class;
+
+    /**
+     * 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;
+
+    /**
+     * The preferred locale of CRS descriptions.
+     */
+    final Locale locale;
+
+    /**
+     * The task where to send request for CRS descriptions, or {@code null} if an error occurred.
+     * In later case, no more background tasks will be scheduled.
+     */
+    private Loader loader;
+
+    /**
+     * Creates a new deferred list and starts a background process for loading CRS codes.
+     */
+    AuthorityCodes(final CRSAuthorityFactory factory, final Locale locale) {
+        this.locale = locale;
+        codes  = new Object[0];
+        loader = new Loader(factory);
+        BackgroundThreads.execute(loader);
+    }
+
+    /**
+     * Returns the number of elements in this list. This method initially returns only the
number of
+     * cached elements. This number may increase progressively as the background loading
progresses.
+     */
+    @Override
+    public int size() {
+        return codes.length;
+    }
+
+    /**
+     * Returns the authority code at given index, eventually with its name.
+     */
+    @Override
+    public Code get(final int index) {
+        final Object value = codes[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;
+        return c;
+    }
+
+    /**
+     * Adds a single code. This method should never be invoked except of 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;
+        beginChange();
+        nextAdd(i, i+1);
+        endChange();
+        return true;
+    }
+
+    /**
+     * 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.
+     */
+    @Override
+    public ObservableValue<String> call(final TableColumn.CellDataFeatures<Code,String>
cell) {
+        return getName(cell.getValue()).getReadOnlyProperty();
+    }
+
+    /**
+     * 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.
+     */
+    final ReadOnlyStringWrapper getName(final Code code) {
+        final ReadOnlyStringWrapper p = code.name();
+        final String name = p.getValue();
+        if (name == null && loader != null) {
+            loader.requestName(code);
+        }
+        return p;
+    }
+
+    /**
+     * Adds new codes in this list and/or updates existing codes with CRS names.
+     * 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.
+     */
+    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);
+        }
+        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);
+                if (name != null) {
+                    ((Code) value).name().set(name);            // The name needs to be set
in JavaFX thread.
+                    nextUpdate(i);
+                }
+            }
+        }
+        nextAdd(s, n);
+        endChange();
+    }
+
+    /**
+     * 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).
+     */
+    private final class Loader extends Task<Object> {
+        /**
+         * The factory to use for creating coordinate reference systems,
+         * or {@code null} if not yet determined.
+         */
+        private CRSAuthorityFactory factory;
+
+        /**
+         * 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.
+         * This list is read and written by two different threads; usages must be synchronized.
+         */
+        private final List<Code> toDescribe;
+
+        /**
+         * {@code true} for loading authority codes in addition of processing {@link #toDescribe},
+         * or {@code false} if codes are already loaded. In later case this task will only
process
+         * the {@link #toDescribe} list.
+         */
+        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.
+         */
+        Loader(final CRSAuthorityFactory factory) {
+            this.factory = factory;
+            toDescribe   = new ArrayList<>();
+            loadCodes    = true;
+        }
+
+        /**
+         * Invoked after a background thread finished its task. Prepares a new background
thread
+         * for loading names (descriptions) for authority codes listed in {@link #toDescribe}.
+         */
+        private Loader(final Loader previous) {
+            factory    = previous.factory;
+            toDescribe = previous.toDescribe;
+            loadCodes  = false;
+        }
+
+        /**
+         * 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.
+         */
+        final void requestName(final Code code) {
+            synchronized (toDescribe) {
+                toDescribe.add(code);
+            }
+            if (!isRunning()) {                         // Include "scheduled" state.
+                BackgroundThreads.execute(this);
+            }
+        }
+
+        /**
+         * 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.
+         */
+        private Map<Code,String> processNameRequests() throws FactoryException {
+            final Code[] snapshot;
+            synchronized (toDescribe) {
+                final int size = toDescribe.size();
+                if (size == 0) return null;
+                snapshot = toDescribe.toArray(new Code[size]);
+                toDescribe.clear();
+            }
+            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));
+            }
+            return updated;
+        }
+
+        /**
+         * 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.
+         *
+         * @return one 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>A {@code Map<Code,String>} which contains the codes for which
the names
+         *         or descriptions have been updated.</li>
+         *   </ul>
+         *
+         * @throws Exception if an error occurred while fetching the codes or the names/descriptions.
+         */
+        @Override
+        protected Object call() throws Exception {
+            long lastTime = System.nanoTime();
+            List<String> codes = Collections.emptyList();
+            try {
+                if (factory == null) {
+                    factory = CRS.getAuthorityFactory(Constants.EPSG);
+                }
+                if (loadCodes) {
+                    codes = new ArrayList<>(100);
+                    final Iterator<String> it = factory.getAuthorityCodes(type).iterator();
+                    while (it.hasNext()) {
+                        codes.add(it.next());
+                        if (System.nanoTime() - lastTime > REFRESH_DELAY) {
+                            final Object[] newCodes = codes.toArray();                //
Snapshot of current content.
+                            codes.clear();
+                            final Map<Code,String> updated = processNameRequests();
  // Must be outside lambda expression.
+                            Platform.runLater(() -> update(newCodes, updated));
+                            lastTime = System.nanoTime();
+                        }
+                    }
+                }
+                /*
+                 * At this point we loaded all authority codes. If there is some remaining
codes,
+                 * returns them immediately for allowing the user interface to be updated
quickly.
+                 * If there is no more codes to return, wait a little bit for giving a chance
to
+                 * the `toDescribe` list to be populated with more requests, then process
them.
+                 */
+                if (codes.isEmpty()) {
+                    Thread.sleep(REFRESH_DELAY / StandardDateFormat.NANOS_PER_MILLISECOND);
+                    return processNameRequests();
+                }
+            } catch (BackingStoreException e) {
+                throw e.unwrapOrRethrow(Exception.class);
+            }
+            return codes;
+        }
+
+        /**
+         * Invoked after the background thread finished to load authority codes.
+         * This method adds the remaining codes to {@link AuthorityCodes} list,
+         * then prepare another background tasks for loading descriptions.
+         */
+        @Override
+        @SuppressWarnings("unchecked")
+        protected void succeeded() {
+            super.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);
+            /*
+             * Prepare the next task for loading description. If new description requests
were posted
+             * between the end of `call()` execution and the start of this `succeeded()`
execution,
+             * starts the new task immediately.
+             */
+            loader = new Loader(this);
+            final boolean isEmpty;
+            synchronized (toDescribe) {
+                isEmpty = toDescribe.isEmpty();
+            }
+            if (!isEmpty) {
+                BackgroundThreads.execute(loader);
+            }
+        }
+
+        /**
+         * Invoked if an error occurred while loading the codes. A pseudo-code is added with
error message
+         * and no more background tasks will be scheduled.
+         */
+        @Override
+        protected void failed() {
+            super.failed();
+            loader = null;
+            final Throwable e = getException();
+            final Code code = new Code(Vocabulary.getResources(locale).getString(Vocabulary.Keys.Errors));
+            String message = Exceptions.getLocalizedMessage(e, locale);
+            if (message == null) {
+                message = e.toString();
+            }
+            code.name().set(message);
+            add(code);
+        }
+    }
+}
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 765f837..73fd239 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
@@ -16,27 +16,31 @@
  */
 package org.apache.sis.gui.referencing;
 
-import java.util.Set;
-import java.util.List;
-import java.util.ArrayList;
 import java.util.Locale;
-import javafx.beans.property.SimpleObjectProperty;
-import javafx.collections.FXCollections;
-import javafx.concurrent.Task;
+import java.util.function.Predicate;
+import javafx.collections.ObservableList;
+import javafx.collections.transformation.FilteredList;
+import javafx.event.ActionEvent;
+import javafx.geometry.Insets;
+import javafx.geometry.Pos;
 import javafx.scene.control.Alert;
+import javafx.scene.control.Button;
 import javafx.scene.control.ButtonType;
 import javafx.scene.control.DialogPane;
 import javafx.scene.control.Label;
-import javafx.scene.control.ProgressIndicator;
 import javafx.scene.control.TableColumn;
 import javafx.scene.control.TableView;
 import javafx.scene.control.TextField;
-import javafx.scene.input.KeyEvent;
 import javafx.scene.layout.BorderPane;
+import javafx.scene.layout.HBox;
+import javafx.scene.layout.Priority;
 import org.opengis.referencing.crs.CRSAuthorityFactory;
 import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.internal.gui.BackgroundThreads;
+import org.apache.sis.internal.gui.IdentityValueFactory;
+import org.apache.sis.internal.gui.Resources;
+import org.apache.sis.internal.util.Strings;
 import org.apache.sis.util.resources.Vocabulary;
+import org.apache.sis.util.CharSequences;
 import org.apache.sis.referencing.CRS;
 
 
@@ -51,11 +55,6 @@ import org.apache.sis.referencing.CRS;
  */
 public class CRSChooser {
     /**
-     * The preferred locale of CRS descriptions.
-     */
-    private final Locale locale;
-
-    /**
      * The pane where the controls for this CRS chooser will be put.
      */
     final BorderPane content;
@@ -66,120 +65,99 @@ public class CRSChooser {
     private final TextField searchField;
 
     /**
-     * The table showing CRS names together with their codes.
+     * The table showing CRS codes together with their names. Table items are provided by
a background thread.
+     * Items are initially authority codes as {@link Code} instances without {@link Code#name}
value.
+     * Names are completed later when needed.
      */
     private final TableView<Code> table;
 
     /**
-     * Creates chooser proposing all coordinate reference systems from the given factory.
+     * Creates a chooser proposing all coordinate reference systems from the given factory.
      *
      * @param  factory  the factory to use for creating coordinate reference systems, or
{@code null}
      *                  for the {@linkplain CRS#getAuthorityFactory(String) Apache SIS default
factory}.
      */
     public CRSChooser(final CRSAuthorityFactory factory) {
-        locale = Locale.getDefault();
-        table  = new TableView<>();
-        /*
-         * Loading of all CRS codes may take about one second.
-         * Following put an animation will the CRS are loading.
-         *
-         * TODO: use deferred loading instead.
-         */
-        final ProgressIndicator loading = new ProgressIndicator();
-        loading.setMaxWidth(60);
-        loading.setMaxHeight(60);
-        loading.setProgress(-1);
-        table.setPlaceholder(loading);
+        final Locale locale = Locale.getDefault();
+        final AuthorityCodes codeList = new AuthorityCodes(factory, locale);
+        table = new TableView<>(codeList);
 
         final Vocabulary vocabulary = Vocabulary.getResources(locale);
+        final TableColumn<Code,Code>   codes = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Code));
         final TableColumn<Code,String> names = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Name));
-        final TableColumn<Code,String> codes = new TableColumn<>(vocabulary.getString(Vocabulary.Keys.Code));
-        codes.setPrefWidth(150);
-        codes.setCellValueFactory((TableColumn.CellDataFeatures<Code,String> p) ->
new SimpleObjectProperty<>(p.getValue().code));
-        names.setCellValueFactory((TableColumn.CellDataFeatures<Code,String> p) ->
new SimpleObjectProperty<>(p.getValue().name(locale)));
-
-        table.getColumns().setAll(names, codes);
+        names.setCellValueFactory(codeList);
+        codes.setCellValueFactory(IdentityValueFactory.instance());
+        codes.setCellFactory(Code.Cell::new);
+        codes.setMinWidth ( 60);            // Will be the initial size of this column.
+        codes.setMaxWidth (120);            // Seems to be required for preventing `codes`
to be as large as `names`.
+        table.setPrefWidth(500);
+        table.getColumns().setAll(codes, names);
         table.setColumnResizePolicy(TableView.CONSTRAINED_RESIZE_POLICY);
-        table.setTableMenuButtonVisible(false);
 
-        content = new BorderPane();
         searchField = new TextField();
-        searchField.addEventHandler(KeyEvent.KEY_PRESSED, (KeyEvent event) -> {
-            searchCRS(searchField.getText());
+        searchField.setOnAction((ActionEvent event) -> {
+            filter(searchField.getText());
         });
-        content.setCenter(table);
-
-        BackgroundThreads.execute(new Loader(factory));
-    }
-
-    private final class Loader extends Task<List<Code>> {
-        private CRSAuthorityFactory factory;
-
-        Loader(final CRSAuthorityFactory factory) {
-            this.factory = factory;
-        }
-
-        @Override
-        protected List<Code> call() throws Exception {
-            if (factory == null) {
-                factory = CRS.getAuthorityFactory(null);
-            }
-            final Set<String> strs = factory.getAuthorityCodes(CoordinateReferenceSystem.class);
-            final List<Code> codes = new ArrayList<>();
-            for (final String code : strs) {
-                codes.add(new Code(factory, code));
-            }
-            return codes;
-        }
-
-        @Override
-        protected void succeeded() {
-            table.setItems(FXCollections.observableArrayList(getValue()));
-            table.setPlaceholder(new Label(""));
-        }
-
-        @Override
-        protected void failed() {
-            error(getException());
-        }
-    }
+        final Resources i18n = Resources.forLocale(locale);
+        final Label label = new Label(i18n.getString(Resources.Keys.Filter));
+        final Button info = new Button("\uD83D\uDEC8");         // Unicode U+1F6C8: Circled
Information Source
+        label.setLabelFor(searchField);
+        HBox.setHgrow(searchField, Priority.ALWAYS);
+        final HBox bar = new HBox(label, searchField, info);
+        bar.setSpacing(9);
+        bar.setAlignment(Pos.BASELINE_LEFT);
+        BorderPane.setMargin(bar, new Insets(0, 0, 9, 0));
 
-    public void searchCRS(final String searchword){
-        filter(searchword);
-    }
-
-    private static void error(final Throwable e) {
-        // TODO
+        content = new BorderPane();
+        content.setCenter(table);
+        content.setTop(bar);
     }
 
     /**
-     * Display only the CRS name that contains the specified keywords. The {@code keywords}
-     * argument is a space-separated list, usually provided by the user after he pressed
the
-     * "Search" button.
+     * Displays only the CRS whose names contains the specified keywords. The {@code keywords}
+     * argument is a space-separated list provided by the user after he pressed "Enter" key.
      *
-     * @param keywords space-separated list of keywords to look for.
+     * @param  keywords  space-separated list of keywords to look for.
      */
     private void filter(String keywords) {
-        final List<Code> allValues = table.getItems();
-        List<Code> model = allValues;
+        final ObservableList<Code> items = table.getItems();
+        final AuthorityCodes allCodes;
+        FilteredList<Code> filtered;
+        if (items instanceof AuthorityCodes) {
+            allCodes = (AuthorityCodes) items;
+            filtered = null;
+        } else {
+            filtered = (FilteredList<Code>) items;
+            allCodes = (AuthorityCodes) filtered.getSource();
+        }
+        keywords = Strings.trimOrNull(keywords);
         if (keywords != null) {
-            keywords = keywords.toLowerCase(locale).trim();
-            final String[] tokens = keywords.split("\\s+");
+            keywords = keywords.toLowerCase(allCodes.locale);
+            final String[] tokens = (String[]) CharSequences.split(keywords, ' ');
             if (tokens.length != 0) {
-                model = new ArrayList<>();
-                scan:
-                for (Code code : allValues) {
-                    final String name = code.toString().toLowerCase(locale);
-                    for (int j=0; j<tokens.length; j++) {
-                        if (!name.contains(tokens[j])) {
-                            continue scan;
+                final Predicate<Code> p = (code) -> {
+                    String name = allCodes.getName(code).getValue();
+                    if (name == null) {
+                        return false;
+                    }
+                    name = name.toLowerCase(allCodes.locale);
+                    for (final String token : tokens) {
+                        if (!name.contains(token)) {
+                            return false;
                         }
                     }
-                    model.add(code);
+                    return true;
+                };
+                if (filtered == null) {
+                    filtered = new FilteredList<>(allCodes, p);
+                    table.setItems(filtered);
+                } else {
+                    filtered.setPredicate(p);
                 }
+                return;
             }
         }
-        table.getItems().setAll(model);
+        table.setItems(allCodes);
     }
 
     /**
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
index 67bc212..ad67412 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/referencing/Code.java
@@ -16,16 +16,15 @@
  */
 package org.apache.sis.gui.referencing;
 
-import java.util.Locale;
-import org.opengis.util.FactoryException;
-import org.opengis.referencing.crs.CRSAuthorityFactory;
-import org.opengis.referencing.crs.CoordinateReferenceSystem;
-import org.apache.sis.util.Exceptions;
+import javafx.geometry.Pos;
+import javafx.scene.control.TableCell;
+import javafx.scene.control.TableColumn;
+import javafx.beans.property.ReadOnlyStringWrapper;
+import org.apache.sis.internal.gui.Styles;
 
 
 /**
- * Stores the code of a coordinate reference system (CRS) together with its description.
- * The description will be fetched when first needed and returned by {@link #toString()}.
+ * Stores the code of a coordinate reference system (CRS) together with its name or description.
  *
  * @author  Martin Desruisseaux (Geomatys)
  * @author  Johann Sorel (Geomatys)
@@ -36,56 +35,77 @@ import org.apache.sis.util.Exceptions;
 final class Code {
     /**
      * The CRS code. Usually defined by EPSG, but other authorities are allowed.
+     * This is the value returned by {@link #toString()}.
      */
     final String code;
 
     /**
-     * The CRS object name for the {@linkplain #code}, fetched when first needed.
+     * The CRS object description for the {@linkplain #code}.
      * In Apache SIS implementation of EPSG factory, this is the CRS name.
      */
-    private String name;
+    private ReadOnlyStringWrapper name;
 
     /**
-     * The object returned by {@link #crs()}, cached for reuse.
+     * Creates a code from the specified value.
      */
-    private CoordinateReferenceSystem crs;
+    Code(final String code) {
+        this.code = code;
+    }
 
     /**
-     * The authority factory to use for fetching the name.
+     * Returns the property where to store the name or description of this authority code.
      */
-    private final CRSAuthorityFactory factory;
+    final ReadOnlyStringWrapper name() {
+        if (name == null) {
+            name = new ReadOnlyStringWrapper();
+        }
+        return name;
+    }
 
     /**
-     * Creates a code from the specified value.
+     * Returns {@link #code}. This behavior is required for {@link CRSChooser} since it
+     * will invoke {@link Object#toString()} directly for the column of authority codes.
      */
-    Code(final CRSAuthorityFactory factory, final String code) {
-        this.factory = factory;
-        this.code    = code;
+    @Override
+    public String toString() {
+        return code;
     }
 
-    /**
-     * Creates the object identified by code.
+    /*
+     * Do not override equals(Object) and hashCode(). We rely on identity comparisons
+     * when using this object as keys in HashMap.
      */
-    CoordinateReferenceSystem crs() throws FactoryException {
-        if (crs == null) {
-            crs = factory.createCoordinateReferenceSystem(code);
-        }
-        return crs;
-    }
 
     /**
-     * Returns a description of the object. This method fetches the description when first
needed.
-     * If the operation fails, the exception message will be used as a description.
-     *
-     * @param  locale  the desired locale, or {@code null} for the default locale.
-     * @return the object name in the given locale if possible.
+     * A cell displaying a code value.
      */
-    String name(final Locale locale) {
-        if (name == null) try {
-            name = factory.getDescriptionText(code).toString(locale);
-        } catch (FactoryException e) {
-            name = Exceptions.getLocalizedMessage(e, locale);
+    static final class Cell extends TableCell<Code,Code> {
+        /**
+         * Creates a new cell for feature property value.
+         *
+         * @param  column  the column where the cell will be shown.
+         */
+        Cell(final TableColumn<Code,Code> column) {
+            // Column not used at this time, but we need it in method signature.
+            setAlignment(Pos.BASELINE_RIGHT);
+            setTextFill(Styles.CODE_TEXT);
+        }
+
+        /**
+         * Invoked when a new value needs to be show.
+         *
+         * @todo I didn't found how to get white text color when the row is selected.
+         *       Current color (blue~gray on blue) is hard to read.
+         */
+        @Override
+        protected void updateItem(final Code value, final boolean empty) {
+            if (value == getItem()) return;
+            super.updateItem(value, empty);
+            String text = null;
+            if (value != null) {
+                text = value.toString();
+            }
+            setText(text);
         }
-        return name;
     }
 }
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
index 7240f4d..6076310 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.java
@@ -146,6 +146,11 @@ public final class Resources extends IndexedResourceBundle {
         public static final short File = 10;
 
         /**
+         * Filter:
+         */
+        public static final short Filter = 34;
+
+        /**
          * Geospatial data files
          */
         public static final short GeospatialFiles = 4;
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
index 425bf5d..9824bb6 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources.properties
@@ -38,6 +38,7 @@ ErrorOpeningFile       = Error opening file
 Exit                   = Exit
 Extent                 = Extent:
 File                   = File
+Filter                 = Filter:
 GeospatialFiles        = Geospatial data files
 Loading                = Loading\u2026
 Metadata               = Metadata
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
index 7be1eb0..bbce0ce 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Resources_fr.properties
@@ -43,6 +43,7 @@ ErrorOpeningFile       = Erreur \u00e0 l\u2019ouverture du fichier
 Exit                   = Quitter
 Extent                 = \u00c9tendue\u00a0:
 File                   = Fichier
+Filter                 = Filtrer\u00a0:
 GeospatialFiles        = Fichiers de donn\u00e9es g\u00e9ospatiales
 Loading                = Chargement\u2026
 Metadata               = Metadonn\u00e9es
diff --git a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
index 3dfdbad..0f160cd 100644
--- a/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
+++ b/application/sis-javafx/src/main/java/org/apache/sis/internal/gui/Styles.java
@@ -51,6 +51,11 @@ public final class Styles {
     public static final Color LOADING_TEXT = Color.BLUE;
 
     /**
+     * Color of text for authority codes.
+     */
+    public static final Color CODE_TEXT = Color.LIGHTSLATEGREY;
+
+    /**
      * Color of text shown in place of data that we failed to load.
      */
     public static final Color ERROR_TEXT = Color.RED;
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
index f26d0d8..3f9ec4f 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/EPSGDataAccess.java
@@ -103,6 +103,7 @@ import org.apache.sis.referencing.operation.transform.DefaultMathTransformFactor
 import org.apache.sis.referencing.factory.FactoryDataException;
 import org.apache.sis.referencing.factory.GeodeticAuthorityFactory;
 import org.apache.sis.referencing.factory.IdentifiedObjectFinder;
+import org.apache.sis.util.collection.BackingStoreException;
 import org.apache.sis.util.iso.SimpleInternationalString;
 import org.apache.sis.util.resources.Vocabulary;
 import org.apache.sis.util.resources.Errors;
@@ -156,7 +157,7 @@ import static org.apache.sis.internal.referencing.ServicesForMetadata.CONNECTION
  * @author  Matthias Basler
  * @author  Andrea Aime (TOPP)
  * @author  Johann Sorel (Geomatys)
- * @version 1.0
+ * @version 1.1
  *
  * @see <a href="http://sis.apache.org/tables/CoordinateReferenceSystems.html">List
of authority codes</a>
  *
@@ -529,6 +530,14 @@ addURIs:    for (int i=0; ; i++) {
 
     /**
      * Returns a map of EPSG authority codes as keys and object names as values.
+     * The cautions documented in {@link #getAuthorityCodes(Class)} apply also to this map.
+     *
+     * @todo We may need to give some public access to this map if callers need descriptions
+     *       for other kinds of object than CRS. Current {@link #getDescriptionText(String)}
+     *       implementation selects CRS if the same code is used by many kinds of objects.
+     *
+     * @see #getAuthorityCodes(Class)
+     * @see #getDescriptionText(String)
      */
     private synchronized Map<String,String> getCodeMap(final Class<?> type) throws
SQLException {
         CloseableReference<AuthorityCodes> reference = authorityCodes.get(type);
@@ -625,6 +634,8 @@ addURIs:    for (int i=0; ; i++) {
             }
         } catch (SQLException exception) {
             throw new FactoryException(exception.getLocalizedMessage(), exception);
+        } catch (BackingStoreException exception) {       // Cause is SQLException.
+            throw new FactoryException(exception.getLocalizedMessage(), exception.getCause());
         }
         throw noSuchAuthorityCode(IdentifiedObject.class, code);
     }
diff --git a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
index 6ff2f51..f900615 100644
--- a/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
+++ b/core/sis-referencing/src/main/java/org/apache/sis/referencing/factory/sql/package-info.java
@@ -82,7 +82,7 @@
  * @author  Jody Garnett (Refractions)
  * @author  Didier Richard (IGN)
  * @author  John Grange
- * @version 1.0
+ * @version 1.1
  *
  * @see org.apache.sis.metadata.sql
  *


Mime
View raw message