Skip to content

Commit 4929dde

Browse files
committed
Bring back file URI support in open dialog
Before some of my older refactoring changes, FilePathPreProcessor was used to allow to open "file:" URIs and to also decode URL-encoded file names. But I've removed it in the Open dialog, as it was pretty hacky and could result in not opening files in some cases. So I've made some changes to the FilePathPreProcessor: * If a file exists on disk at the specified path, no conversion happens. * URL-decoding of paths only happens, if no file at the original path exists and there is a file at the decoded path. * File URI handling is now done via the URI and File classes, which should make it more robust. With that in mind, there was still a problem with the Open dialog. JFileChooser only returns a File, not the path provided in the dialog. And there doesn't seem to be a way to get it without UI class hackery. In the original implementation it searched for "file:" within the path and discarded everything before it. This comes from the fact, that if, for example, you put "file:///C:/3142b600.pdf" in the dialog, you would get something like this from the File API: "C:\selected_dir\file:\C:\3142b600.pdf" To handle this in a less hacky way, there is an EnhancedFileSystemView now, which is passed to the JFileChooser. It just wraps the original FileSystemView, but in the File creation methods, which involve String file names or paths (createFileObject, getChild), they now go through the FilePathPreProcessor. This way all the path transformation will happen before File conversion, which is much cleaner.
1 parent 984f9e0 commit 4929dde

File tree

7 files changed

+1179
-117
lines changed

7 files changed

+1179
-117
lines changed

src/main/java/com/itextpdf/rups/io/PdfFileOpenAction.java

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@ This file is part of the iText (R) project.
4444

4545
import com.itextpdf.rups.RupsConfiguration;
4646
import com.itextpdf.rups.io.filters.PdfFilter;
47+
import com.itextpdf.rups.view.EnhancedFileSystemView;
4748

4849
import java.awt.event.ActionEvent;
4950
import java.awt.event.ActionListener;
@@ -83,24 +84,24 @@ public PdfFileOpenAction(Consumer<File> listener, Component parent) {
8384

8485
@Override
8586
public void actionPerformed(ActionEvent evt) {
86-
final JFileChooser fileChooser = new JFileChooser();
87-
setCurrentDirectory(fileChooser);
87+
final JFileChooser fileChooser = new JFileChooser(
88+
getCurrentDirectory(), EnhancedFileSystemView.INSTANCE
89+
);
8890
fileChooser.setFileFilter(PdfFilter.INSTANCE);
8991
if (fileChooser.showOpenDialog(parent) == JFileChooser.APPROVE_OPTION) {
9092
listener.accept(fileChooser.getSelectedFile());
9193
}
9294
}
9395

94-
private static void setCurrentDirectory(JFileChooser fileChooser) {
96+
private static File getCurrentDirectory() {
9597
final File mostRecentOpenFile = RupsConfiguration.INSTANCE.getMruListHandler().peekMostRecent();
9698
if (mostRecentOpenFile != null) {
9799
final File mostRecentDir = mostRecentOpenFile.getParentFile();
98100
if (mostRecentDir != null) {
99-
fileChooser.setCurrentDirectory(mostRecentDir);
100-
return;
101+
return mostRecentDir;
101102
}
102103
}
103104
// Fallback to home dir
104-
fileChooser.setCurrentDirectory(RupsConfiguration.INSTANCE.getHomeFolder());
105+
return RupsConfiguration.INSTANCE.getHomeFolder();
105106
}
106107
}

src/main/java/com/itextpdf/rups/model/FilePathPreProcessor.java

Lines changed: 128 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -42,41 +42,150 @@ This file is part of the iText (R) project.
4242
*/
4343
package com.itextpdf.rups.model;
4444

45+
import java.io.File;
46+
import java.net.URI;
4547
import java.net.URLDecoder;
4648
import java.nio.charset.StandardCharsets;
49+
import java.util.Optional;
4750

4851
/**
49-
* Pre-processes file path to make it suitable for further processing.
52+
* Static file path processor, which allows handling additional use cases:
53+
* <ul>
54+
* <li>URIs with the {@code file} schema.</li>
55+
* <li>URL-encoded paths.</li>
56+
* </ul>
57+
* <p>
58+
* All these additional use cases are only triggered, when it wouldn't prevent
59+
* opening an existing file with the original path name.
60+
* </p>
5061
*/
5162
public final class FilePathPreProcessor {
52-
5363
private FilePathPreProcessor() {
5464
// Empty constructor
5565
}
5666

57-
public static String process(String path) {
58-
if (path == null) {
59-
return null;
67+
/**
68+
* Process the file name within the context of the specified directory.
69+
* Directory will not be included in the output file name string.
70+
*
71+
* @param currentDir current working dir for the test
72+
* @param filename file name to process
73+
*
74+
* @return the processed file name
75+
*/
76+
public static String process(File currentDir, String filename) {
77+
// Just pass empty values through
78+
if (filename == null || filename.isEmpty()) {
79+
return filename;
6080
}
61-
String processedPath = path;
62-
final String fileProtocol = "file://";
63-
if (processedPath.startsWith(fileProtocol)) {
64-
processedPath = processedPath.substring(fileProtocol.length());
81+
// If a file exists at the specified path, just go with it
82+
if (isFile(currentDir, filename)) {
83+
return filename;
6584
}
66-
final String file = "file:";
67-
if (processedPath.contains(file)) {
68-
processedPath = processedPath.substring(processedPath.indexOf(file) + file.length());
85+
// If path is, actually, a file URI, process that as well
86+
final Optional<String> uriFilePath = fromFileUri(filename);
87+
if (uriFilePath.isPresent()) {
88+
return uriFilePath.get();
6989
}
70-
// Check if the path is encoded
71-
if (isUriEncoded(processedPath)) {
72-
processedPath = URLDecoder.decode(processedPath, StandardCharsets.UTF_8);
90+
// Another special case, if string is URL-encoded for some reason and
91+
// is not a URI, then test that as well
92+
final Optional<String> urlEncodedPath = fromUrlEncoded(currentDir, filename);
93+
if (urlEncodedPath.isPresent()) {
94+
return urlEncodedPath.get();
7395
}
74-
return processedPath;
96+
// If not special cases triggered, just return as-is.
97+
return filename;
7598
}
7699

77-
private static boolean isUriEncoded(String path) {
78-
return !path.equals(URLDecoder.decode(path, StandardCharsets.UTF_8));
100+
/**
101+
* Process the path within the context of the current working directory.
102+
*
103+
* @param path path to process
104+
*
105+
* @return the processed path
106+
*/
107+
public static String process(String path) {
108+
return process(null, path);
109+
}
110+
111+
/**
112+
* Decodes a URL-encoded string and returns the decoded string, if is a
113+
* path to an existing file. Otherwise, returns an empty optional.
114+
*
115+
* @param currentDir current working dir for the test
116+
* @param urlEncodedString URL-encoded string
117+
*
118+
* @return the decoded path to the file
119+
*/
120+
private static Optional<String> fromUrlEncoded(File currentDir, String urlEncodedString) {
121+
try {
122+
final String decoded = URLDecoder.decode(urlEncodedString, StandardCharsets.UTF_8);
123+
if (isFile(currentDir, decoded)) {
124+
return Optional.of(decoded);
125+
}
126+
} catch (Exception ignored) {
127+
// Ignored
128+
}
129+
return Optional.empty();
79130
}
80-
}
81131

132+
/**
133+
* Takes a {@code file} schema URI and returns the absolute path to the
134+
* file. If this is not a file URI, returns an empty optional.
135+
*
136+
* @param fileUriString {@code file} schema URI string
137+
*
138+
* @return the absolute path to the file
139+
*/
140+
private static Optional<String> fromFileUri(String fileUriString) {
141+
try {
142+
URI uri = URI.create(fileUriString);
143+
final String scheme = uri.getScheme();
144+
if (!"file".equalsIgnoreCase(scheme)) {
145+
return Optional.empty();
146+
}
147+
/*
148+
* This is some handling for invalid URIs. For example, if you
149+
* want to have URI to, for example, "C:\note.pdf", then these
150+
* cases are valid:
151+
* - file:C:/node.pdf (i.e. host part is omitted)
152+
* - file:/C:/node.pdf (i.e. host part is omitted and path starts with a slash)
153+
* - file:///C:/node.pdf (i.e. host part is empty)
154+
* But this case is not:
155+
* - file://C:/node.pdf (i.e. "C:" is the host part)
156+
* This block should handle this, as this is a common mistake.
157+
*/
158+
if (uri.getRawAuthority() != null || uri.getPath() == null) {
159+
// Skip "file:" at start
160+
uri = URI.create("file:/" + fileUriString.substring(5));
161+
}
162+
String path = new File(uri).getPath();
163+
// Returning the original URI separators for consistency
164+
if (File.separatorChar != '/') {
165+
path = path.replace(File.separatorChar, '/');
166+
}
167+
return Optional.of(path);
168+
} catch (Exception ignored) {
169+
// Ignored
170+
}
171+
return Optional.empty();
172+
}
82173

174+
/**
175+
* Returns whether a regular file exists at the specified path. If there is
176+
* an exception during the check, {@code false} is returned.
177+
*
178+
* @param currentDir current working dir for the test
179+
* @param path path to check
180+
*
181+
* @return whether a regular file exists at the specified path
182+
*/
183+
private static boolean isFile(File currentDir, String path) {
184+
try {
185+
return new File(currentDir, path).isFile();
186+
} catch (Exception ignored) {
187+
// Ignored
188+
}
189+
return false;
190+
}
191+
}

0 commit comments

Comments
 (0)