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
commit 7d270e20b517fb2c1aa40983b68f9752cd261a81 Author: Martin Desruisseaux <[email protected]> AuthorDate: Thu Sep 3 11:43:41 2020 +0200 Refactor the "Copy file path" action in a separated class with better management of File/URI conversions. --- .../org/apache/sis/gui/dataset/CopyAction.java | 137 +++++++++++++++++++++ .../org/apache/sis/gui/dataset/ResourceTree.java | 59 ++------- .../sis/internal/storage/io/IOUtilities.java | 10 +- 3 files changed, 152 insertions(+), 54 deletions(-) diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/CopyAction.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/CopyAction.java new file mode 100644 index 0000000..acdc442 --- /dev/null +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/CopyAction.java @@ -0,0 +1,137 @@ +/* + * 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.dataset; + +import java.util.List; +import java.util.ArrayList; +import java.util.Collections; +import java.nio.file.Path; +import java.io.File; +import java.net.URL; +import java.net.URI; +import java.net.URISyntaxException; +import javafx.event.ActionEvent; +import javafx.event.EventHandler; +import javafx.scene.control.TreeCell; +import javafx.scene.input.Clipboard; +import javafx.scene.input.ClipboardContent; +import org.apache.sis.internal.gui.ExceptionReporter; +import org.apache.sis.internal.storage.ResourceOnFileSystem; +import org.apache.sis.internal.storage.URIDataStore; +import org.apache.sis.storage.DataStoreException; +import org.apache.sis.storage.Resource; + + +/** + * The "Copy file path" action. This class gets the file path of a resource and copies it in the clipboard. + * + * @author Martin Desruisseaux (Geomatys) + * @version 1.1 + * @since 1.1 + * @module + */ +final class CopyAction implements EventHandler<ActionEvent> { + /** + * The cell for which to provide a copy action. + */ + private final TreeCell<Resource> cell; + + /** + * Creates a new instance. + */ + CopyAction(final TreeCell<Resource> source) { + this.cell = source; + } + + /** + * Invoked when the user selected "Copy file path" item in the contextual menu of a {@link ResourceTree} cell. + * This method copies the path of the selected resource to the clipboard. + */ + @Override + public void handle(final ActionEvent event) { + final Resource resource = cell.getItem(); + final Object path; + try { + path = URIDataStore.location(resource); + } catch (DataStoreException e) { + ExceptionReporter.show(null, null, e); + return; + } + /* + * The file path can be given in two forms: as an URI or as a text (named "file" below). + * The textual form is the one that will usually be pasted. We try to provide paths on + * the local file system if possible, converting "file:///" URI if needed. Only if URI + * can not be converted to a local file path, we keep the URI form. + * + * The `uri` is determined in a way opposite to `file`: we convert local path to URI. + * That form is provided in case the path is pasted in applications expecting URI. + */ + Object file = path; + Object uri = null; + try { + if (path instanceof URI) { + uri = path; // Must be first because next line may fail. + file = new File((URI) path); + } else if (path instanceof URL) { + uri = path; // Must be first because next line may fail. + file = new File(((URL) path).toURI()); + } else if (path instanceof File) { + uri = ((File) path).toURI(); + } else if (path instanceof Path) { + uri = ((Path) path).toUri(); // Must be first because next line may fail. + file = ((Path) path).toFile(); + } else if (path instanceof CharSequence) { + uri = new URI(path.toString()); // Stay null if this operation fail. + } else { + file = ""; + } + } catch (URISyntaxException | IllegalArgumentException | UnsupportedOperationException e) { + // Ignore. The `uri` or `text` field that we failed to assign keep its original value. + } + /* + * Above code obtained a single path, considered the main one. But a resource may also be + * associated with many files (some kinds of data are actually provided as a group of files). + * We put in the clipboard all `java.io.File` instances that we can get from the resource. + * This list of files will usually be ignored and only the `file` text will be pasted, + * but it depends on the application where files will be pasted. + */ + List<File> files = null; + if (resource instanceof ResourceOnFileSystem) try { + final Path[] components = ((ResourceOnFileSystem) resource).getComponentFiles(); + if (components != null) { + files = new ArrayList<>(components.length); + for (final Path p : components) try { + if (p != null) files.add(p.toFile()); + } catch (UnsupportedOperationException e) { + // Ignore and try to add other components. + } + } + } catch (DataStoreException e) { + ResourceTree.unexpectedException("copy", e); + } else if (file instanceof File) { + files = Collections.singletonList((File) file); + } + /* + * Put in the clipboard all information that we could get. + */ + final ClipboardContent content = new ClipboardContent(); + content.putString(file.toString()); + if (files != null) content.putFiles(files); + if (uri != null) content.putUrl(uri.toString()); + Clipboard.getSystemClipboard().setContent(content); + } +} diff --git a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java index 12c1e1b..cc923b7 100644 --- a/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java +++ b/application/sis-javafx/src/main/java/org/apache/sis/gui/dataset/ResourceTree.java @@ -17,8 +17,6 @@ package org.apache.sis.gui.dataset; import java.io.File; -import java.nio.file.Path; -import java.net.URI; import java.net.URL; import java.net.MalformedURLException; import java.util.AbstractList; @@ -40,8 +38,6 @@ import javafx.scene.control.TreeView; import javafx.scene.input.DragEvent; import javafx.scene.input.Dragboard; import javafx.scene.input.TransferMode; -import javafx.scene.input.Clipboard; -import javafx.scene.input.ClipboardContent; import javafx.scene.paint.Color; import org.opengis.util.GenericName; import org.opengis.util.InternationalString; @@ -260,46 +256,6 @@ public class ResourceTree extends TreeView<Resource> { } /** - * Performs a "copy" action on the given resource. Current implementation performs only - * a "copy file path" action, but future versions may add more kinds of copy actions. - * - * @param resource the resource to copy. - */ - private static void copy(final Resource resource) { - Object path; - try { - path = URIDataStore.location(resource); - } catch (DataStoreException e) { - ExceptionReporter.show(null, null, e); - return; - } - final ClipboardContent content = new ClipboardContent(); - final boolean isKindOfPath = IOUtilities.isKindOfPath(path); - if (isKindOfPath || path instanceof CharSequence) { - String uri = path.toString(); - String text = uri; - try { - if (path instanceof URI) { - path = new File((URI) path); - } else if (path instanceof Path) { - path = ((Path) path).toFile(); - } - } catch (IllegalArgumentException | UnsupportedOperationException e) { - // Ignore - } - if (path instanceof File) { - content.putFiles(Collections.singletonList((File) path)); - text = path.toString(); - } - if (isKindOfPath) { - content.putUrl(uri); - } - content.putString(text); - } - Clipboard.getSystemClipboard().setContent(content); - } - - /** * Removes the given resource from the tree and closes it if it is a {@link DataStore}. * It is caller's responsibility to ensure that the given resource is not used anymore. * A resource can be removed only if it is a root. If the given resource is not in this @@ -468,6 +424,13 @@ public class ResourceTree extends TreeView<Resource> { } /** + * Reports an unexpected but non-fatal exception in the given method. + */ + static void unexpectedException(final String method, final Exception e) { + Logging.unexpectedException(Logging.getLogger(Modules.APPLICATION), ResourceTree.class, method, e); + } + + /** * The visual appearance of an {@link Item} in a tree. This call gets the cell text from a resource * by a call to {@link ResourceTree#getTitle(Resource, boolean)}. Cells are initially empty; * their content will be specified by {@link TreeView} after construction. @@ -542,9 +505,7 @@ public class ResourceTree extends TreeView<Resource> { menu = new ContextMenu(); final Resources localized = tree.localized(); final MenuItem[] items = new MenuItem[CLOSE + 1]; - items[COPY_PATH] = localized.menu(Resources.Keys.CopyFilePath, (e) -> { - copy(getItem()); - }); + items[COPY_PATH] = localized.menu(Resources.Keys.CopyFilePath, new CopyAction(this)); items[CLOSE] = localized.menu(Resources.Keys.Close, (e) -> { ((ResourceTree) getTreeView()).removeAndClose(getItem()); }); @@ -559,9 +520,9 @@ public class ResourceTree extends TreeView<Resource> { path = URIDataStore.location(resource); } catch (DataStoreException e) { path = null; - Logging.unexpectedException(Logging.getLogger(Modules.APPLICATION), URIDataStore.class, "location", e); + unexpectedException("updateItem", e); } - menu.getItems().get(COPY_PATH).setDisable(!(IOUtilities.isKindOfPath(path) || path instanceof CharSequence)); + menu.getItems().get(COPY_PATH).setDisable(!IOUtilities.isKindOfPath(path)); } setContextMenu(menu); } diff --git a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java index 7ce731b..fae94a7 100644 --- a/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java +++ b/storage/sis-storage/src/main/java/org/apache/sis/internal/storage/io/IOUtilities.java @@ -71,9 +71,8 @@ public final class IOUtilities extends Static { } /** - * Returns {@code true} if the given object is a {@link Path}, {@link File}, {@link URL} or {@link URI}. - * Note that this method does not returns {@code true} for {@link CharSequence}; that type needs to be - * verified by the caller if desired. + * Returns {@code true} if the given object is a {@link Path}, {@link File}, {@link URL}, {@link URI} + * or {@link CharSequence}. They are the types accepted by methods such as {@link #filename(Object)}. * * @param path the object to verify. * @return whether the given object is of known type. @@ -81,8 +80,9 @@ public final class IOUtilities extends Static { * @since 1.1 */ public static boolean isKindOfPath(final Object path) { - return (path instanceof URI) || (path instanceof URL) || // Test final classes first. - (path instanceof Path) || (path instanceof File); + return (path instanceof URI) || (path instanceof URL) || // Test final classes first. + (path instanceof Path) || (path instanceof File) || + (path instanceof CharSequence); } /**
