I extended FileSystemView and overwrote every method in this class. The model looks like this:
public class RemoteSystemFilesView extends FileSystemView {
private IDirectoryService directoryService;
public RemoteSystemFilesView(IDirectoryService aDirectoryService){
this.directoryService = aDirectoryService;
}
....
}
The directoryService object returns directories from the remote UNIX server. Then, I create JFileChooser.
JFileChooser fc = new JFileChooser(new RemoteSystemFilesView(new DirectoryService()));
int returnVal = fc.showOpenDialog(this);
The dialog shows remote dirs and files correctly, but then I doubleClick on one of the displayed folders, I expect to navigate into that folder, but instead folder path appears in the field "File name" and that's it. I can't go to any other directory except root (/). Should I implement something else also in JFileChooser, not just in FileSystemView?
The problem might be that your FileSystemView is actually returning plain java.io.File objects.
Instead try to return a VirtualFile wrapper object that extends java.io.File and returns true for the public boolean exists() and wraps returns VirtualFile instead of java.io.File for all the necessary methods.
This is an example of a VirtualFileSystem that I developed. It uses java.nio.Path because my code is mainly based in them. I hope it gives you a good starting point for understanding how to modify your code.
private static class VirtualFileSystemView extends FileSystemView {
final Path base;
final Set<Path> choices;
private VirtualFileSystemView(final Path base,
final Set<Path> choices) {
this.base = base;
this.choices = choices;
}
#Override
protected File createFileSystemRoot(File f) {
return new VirtualFile(f);
}
#Override
public boolean isComputerNode(File dir) {
return false;
}
#Override
public boolean isFloppyDrive(File dir) {
return false;
}
#Override
public boolean isDrive(File dir) {
return false;
}
#Override
public Icon getSystemIcon(File f) {
return null;
}
#Override
public String getSystemTypeDescription(File f) {
return f.toPath().toString();
}
#Override
public String getSystemDisplayName(File f) {
return f.getName();
}
#Override
public File getParentDirectory(final File dir) {
return new VirtualFile(dir.getParentFile());
}
#Override
public File[] getFiles(final File dir, boolean useFileHiding) {
final List<File> files = new ArrayList<>(choices.size());
choices.stream()
.filter((path) -> (path.getParent().equals(dir.toPath()))).
forEach((path) -> {
files.add(new VirtualFile(path.toFile()));
});
return files.toArray(new File[files.size()]);
}
#Override
public File createFileObject(final String path) {
return new VirtualFile(path);
}
#Override
public File createFileObject(final File dir, final String filename) {
Path fileObject;
if (dir != null) {
fileObject = Paths.get(dir.toPath().toString(), filename);
} else {
fileObject = Paths.get(filename);
}
return new VirtualFile(fileObject.toFile());
}
#Override
public File getDefaultDirectory() {
return new VirtualFile(base.toFile());
}
#Override
public File getHomeDirectory() {
return new VirtualFile(base.toFile());
}
#Override
public File[] getRoots() {
final List<File> files = new ArrayList<>(choices.size());
files.add(new VirtualFile(base.toFile()));
return files.toArray(new File[files.size()]);
}
#Override
public boolean isFileSystemRoot(final File dir) {
boolean isRoot = dir.toPath().getParent() == null;
return isRoot;
}
#Override
public boolean isHiddenFile(final File f) {
return false;
}
#Override
public boolean isFileSystem(final File f) {
return !isFileSystemRoot(f);
}
#Override
public File getChild(final File parent, final String fileName) {
return new VirtualFile(parent, fileName);
}
#Override
public boolean isParent(final File folder, final File file) {
return file.toPath().getParent().equals(folder.toPath());
}
#Override
public Boolean isTraversable(final File f) {
boolean isTraversable = false;
for (final Path path : choices) {
if (path.startsWith(f.toPath())) {
isTraversable = true;
break;
}
}
return isTraversable;
}
#Override
public boolean isRoot(final File f) {
boolean isRoot = false;
for (final Path path : choices) {
if (path.getParent().equals(f.toPath())) {
isRoot = true;
}
}
return isRoot;
}
#Override
public File createNewFolder(final File containingDir) throws IOException {
return new VirtualFile(containingDir);
}
private class VirtualFile extends File {
private static final long serialVersionUID = -1752685357864733168L;
private VirtualFile(final File file) {
super(file.toString());
}
private VirtualFile(String pathname) {
super(pathname);
}
private VirtualFile(String parent, String child) {
super(parent, child);
}
private VirtualFile(File parent, String child) {
super(parent, child);
}
#Override
public boolean exists() {
return true;
}
#Override
public boolean isDirectory() {
return VirtualFileSystemView.this.isTraversable(this);
}
#Override
public File getCanonicalFile() throws IOException {
return new VirtualFile(super.getCanonicalFile());
}
#Override
public File getAbsoluteFile() {
return new VirtualFile(super.getAbsoluteFile());
}
#Override
public File getParentFile() {
File parent = super.getParentFile();
if (parent != null) {
parent = new VirtualFile(super.getParentFile());
}
return parent;
}
}
}
Related
Does the MacOS clipboard use some super-secret DataFlavor that Java apps can only read from, not write to?
I've written an app (Java 15) that copies files to the clipboard, and I can retrieve these files from within my app. But when I go to Finder, it says Clipboard contents: unknown. Here's the code:
public static class FileTransferable implements Transferable {
private final List listOfFiles;
public FileTransferable(List listOfFiles) {
this.listOfFiles = listOfFiles;
}
#Override
public DataFlavor[] getTransferDataFlavors() {
return new DataFlavor[]{DataFlavor.javaFileListFlavor};
}
#Override
public boolean isDataFlavorSupported(DataFlavor flavor) {
return DataFlavor.javaFileListFlavor.equals(flavor);
}
#Override
public Object getTransferData(DataFlavor flavor) throws UnsupportedFlavorException, IOException {
return listOfFiles;
}
}
public void copyItems() {
ArrayList<File> files = new ArrayList<>();
files.add(new File("/Users/fred/Public/IMG0010.HEIC"));
files.add(new File("/Users/fred/Public/IMG0011.HEIC"));
files.add(new File("/Users/fred/Public/IMG0012.HEIC"));
Clipboard clip = Toolkit.getDefaultToolkit().getSystemClipboard();
clip.setContents(new FileTransferable(files), null);
}
Is there any way around this?
I need find all the java files in a directory
private void search(File directory) {
if (directory.isDirectory()) {
File[] javaFilesLs = directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".java");
// return name.toLowerCase().endsWith(".java") || dir.isDirectory();
}
});
if (directory.canRead()) {
assert javaFilesLs != null;
for (File temp : javaFilesLs) {
if (temp.isDirectory()) {
search(temp);
} else {
fileList.add(temp.getAbsolutePath());
}
}
}
}
}
When I use the commented line it finds the subdirectory and all the files not only ".java" files.
The reason why you get all paths using the commented line, is that dir.isDirectory() will return true for all files.
Take a look at the documentation of FilenameFilter. It specifies that dir is "the directory in which the file was found."
So instead of looking at dir, you must test if name represents a directory. There may be smarter methods, but it can be done like this:
new File(dir.toPath().toString(), name).isDirectory() // returns true for directories
The whole snippet thus looks like this:
private void search(File directory) {
if (directory.isDirectory()) {
File[] javaFilesLs = directory.listFiles(new FilenameFilter() {
public boolean accept(File dir, String name) {
return name.toLowerCase().endsWith(".java") || new File(dir.toPath().toString(), name).isDirectory();
}
});
if (directory.canRead()) {
assert javaFilesLs != null;
for (File temp : javaFilesLs) {
if (temp.isDirectory()) {
search(temp);
} else {
fileList.add(temp.getAbsolutePath());
}
}
}
}
}
Alternatively, Java 8 adds Files.walk which implements the recursion for you as a Stream.
private void search(File directory) throws IOException {
Files.walk(directory.toPath())
.filter(f -> {
return f.getFileName().toString().endsWith(".java");
})
.forEach(f -> fileList.add(f.toFile().getAbsolutePath()));
}
I have this piece of code:
#Override
public void inform(String data) {
if (data.equals(C.SubscriptionEvents.WINDOW_CLOSED)) {
File tempFolder = new File("temp");
File[] files = tempFolder.listFiles();
if (files != null) {
for (File f : files) f.delete();
}
} else if (data.equals(C.Controller.Commands.SELECT_MODE_VERTICES)) {
MainModel.setCurrentMode(Mode.VERTICES);
display.getInfoSection().repaint();
} else if (data.equals(C.Controller.Commands.SELECT_MODE_LINES)) {
MainModel.setCurrentMode(Mode.LINES);
display.getInfoSection().repaint();
} else if (data.equals(C.Controller.Commands.SELECT_MODE_SECTORS)) {
MainModel.setCurrentMode(Mode.SECTORS);
display.getInfoSection().repaint();
}
}
The method gets a string which is a Command name. According to the name, it does a specified behavior. As you can see, it starts to have too much elseifs (and probably will have more). This method belongs to an interface which is shared between packages so I decided to make the parameter as string.
Is there a better way to do it to avoid the method to be huge when there will be lots of commands (this includes switch case too)?
You can check Command pattern https://www.baeldung.com/java-command-pattern but it may require quite extensive refactoring and make method inform() accept objects of type Command
You could use an enum class, as follows:
public enum Command {
WINDOW_CLOSED { //C.SubscriptionEvents.WINDOW_CLOSED
public void invoke() {
File tempFolder = new File("temp");
File[] files = tempFolder.listFiles();
if (files != null) {
for (File f : files) f.delete();
}
}
}
,SELECT_MODE_VERTICES { // C.Controller.Commands.SELECT_MODE_VERTICES
public void invoke() {
MainModel.setCurrentMode(Mode.VERTICES);
display.getInfoSection().repaint();
}
}
,SELECT_MODE_LINES { // C.Controller.Commands.SELECT_MODE_LINES
public void invoke() {
MainModel.setCurrentMode(Mode.LINES);
display.getInfoSection().repaint();
}
}
,SELECT_MODE_SECTORS { // C.Controller.Commands.SELECT_MODE_SECTORS
public void invoke() {
MainModel.setCurrentMode(Mode.SECTORS);
display.getInfoSection().repaint();
}
}
;
public abstract void invoke();
}
Then replace the guts of your function above with this:
#Override
public void inform(String data) {
Command.valueOf(data).invoke();
}
The names of your 'enum' values must exactly match the string values of the various things you are testing for in your original code (e.g. C.SubscriptionEvents.WINDOW_CLOSED, C.SubscriptionEvents.WINDOW_CLOSED)
What about to use Enum instead of if...else?:
enum Event {
NULL(null, context -> { }),
WINDOWS_CLOSE(C.SubscriptionEvents.WINDOW_CLOSED, context -> {
File tempFolder = new File("temp");
File[] files = tempFolder.listFiles();
if (files != null) {
for (File f : files) f.delete();
}
}),
SELECT_MODE_VERTICES(C.Controller.Commands.SELECT_MODE_VERTICES, context -> {
MainModel.setCurrentMode(Mode.VERTICES);
display.getInfoSection().repaint();
}),
SELECT_MODE_LINES(C.Controller.Commands.SELECT_MODE_VERTICES, context -> {
MainModel.setCurrentMode(Mode.LINES);
display.getInfoSection().repaint();
}),
SELECT_MODE_SECTORS(C.Controller.Commands.SELECT_MODE_SECTORS, context -> {
MainModel.setCurrentMode(Mode.SECTORS);
display.getInfoSection().repaint();
});
private final String id;
private final Consumer<Foo> consumer;
Event(String id, Consumer<Foo> consumer) {
this.id = id;
this.consumer = consumer;
}
public final void accept(Foo context) {
consumer.accept(context);
}
public static Event selectEvent(String data) {
for (Event event : values())
if (event.id.equals(data))
return event;
return NULL;
}
}
And your code will be look like this one:
Event.selectEvent(data).accept(this);
You could simplify it this way...
#Override
public void inform(String data) {
Map<String, String> map = new HashMap<String, int>();
map.put(C.Controller.Commands.SELECT_MODE_VERTICES, Mode.VERTICES);
map.put(C.Controller.Commands.SELECT_MODE_LINES), Mode.LINES);
map.put(C.Controller.Commands.SELECT_MODE_SECTORS, Mode.SECTORS);
if (data.equals(C.SubscriptionEvents.WINDOW_CLOSED)) {
File tempFolder = new File("temp");
File[] files = tempFolder.listFiles();
if (files != null) {
for (File f : files) f.delete();
}
} else if (map.containsKey(data)) {
MainModel.setCurrentMode(map.get(key));
display.getInfoSection().repaint();
}
}
I am compiling and loading classes in memory with JavaCompiler.
JavaCompiler compiler = ToolProvider.getSystemJavaCompiler();
JavaCompiler.CompilationTask compile = compiler.getTask(null, inMemoryFileManager, diagnostics, null, annotationClasses, compilationUnits);
if (!compile.call()) {
diagnostics.getDiagnostics().forEach(System.err::println);
}
I am using a similar Filemanager and SimpleJavaObject as in this example. I modified it slightly to be able to handle multiple files. If you need my source code please let me know.
The code above runs fine as long as annotationClasses is null.
When I try to add classes to annotation processing however I get the following error:
Class names, '<classNames>', are only accepted if annotation processing is explicitly requested
For classNames I tried Main, Main.java, package.Main and package.Main.java. They all result in the same error.
I know this is a very specific scenario. Did anybody try to do something similar and can help me?
Edit:
/* Container for a Java compilation unit (ie Java source) in memory. */
private static class CompilationUnit extends SimpleJavaFileObject {
private final String source;
public CompilationUnit(String className, String source) {
super(URI.create("file:///" + className.replaceAll("\\.", "/") + ".java"), Kind.SOURCE);
this.source = source;
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return source;
}
#Override
public OutputStream openOutputStream() {
throw new IllegalStateException();
}
#Override
public InputStream openInputStream() {
return new ByteArrayInputStream(source.getBytes());
}
}
/* Container for Java byte code in memory. */
private static class ByteJavaFileObject extends SimpleJavaFileObject {
private ByteArrayOutputStream byteArrayOutputStream;
public ByteJavaFileObject(String className) {
super(URI.create("byte:///" + className.replaceAll("\\.", "/") + ".class"), Kind.CLASS);
}
#Override
public CharSequence getCharContent(boolean ignoreEncodingErrors) {
return null;
}
#Override
public OutputStream openOutputStream() {
byteArrayOutputStream = new ByteArrayOutputStream();
return byteArrayOutputStream;
}
#Override
public InputStream openInputStream() {
return null;
}
public byte[] getByteCode() {
return byteArrayOutputStream.toByteArray();
}
}
private static class InMemoryFileManager extends ForwardingJavaFileManager {
private final InMemoryClassLoader inMemoryClassLoader;
public InMemoryFileManager(JavaCompiler compiler, Map<String, ByteJavaFileObject> fileObjects) {
super(compiler.getStandardFileManager(null, null, null));
inMemoryClassLoader = new InMemoryClassLoader(fileObjects);
}
#Override
public JavaFileObject getJavaFileForOutput(Location notUsed, String className, JavaFileObject.Kind kind, FileObject sibling) throws IOException {
return inMemoryClassLoader.getFileObject(className);
}
#Override
public ClassLoader getClassLoader(Location location) {
return inMemoryClassLoader;
}
public InMemoryClassLoader getClassLoader() {
return inMemoryClassLoader;
}
}
private static class InMemoryClassLoader extends ClassLoader {
private final Map<String, ByteJavaFileObject> fileObjects;
public InMemoryClassLoader(Map<String, ByteJavaFileObject> fileObjects) {
this.fileObjects = fileObjects;
}
#Override
protected Class<?> findClass(String className) throws ClassNotFoundException {
ByteJavaFileObject fileObject = fileObjects.get(className);
return defineClass(className, fileObject.getByteCode(), 0, fileObject.getByteCode().length);
}
JavaFileObject getFileObject(String className) {
return fileObjects.get(className);
}
}
I have a problem with Zip File System Provider: If the zip file is on a remote drive (mapped or not seems to be irrelevant), the virtual file system is readonly, although the file itself is not. I wrote a minimal sample code:
public static void main(String[] args) throws IOException {
File workingDir = new File(args[0]);
File source = new File(workingDir, "in.zip");
File target = new File(workingDir, "out.zip");
Files.copy(source.toPath(), target.toPath(), StandardCopyOption.REPLACE_EXISTING);
try (FileSystem zipfs = FileSystems.newFileSystem(target.toPath(), null)) {
Path pathInZipfile = zipfs.getPath("test.xml");
System.out.println("zipfile writable: " + target.canWrite());
System.out.println("zipFS writable: " + !zipfs.isReadOnly());
Files.delete(pathInZipfile);
System.out.println("File successfully deleted");
} catch (IOException e) {
e.printStackTrace();
}
}
If workingDir is a local directory, everything works fine. However, if it is a (mapped) remote drive, i get:
zipfile writable: true
zipFS writable: false
Exception in thread "main" java.nio.file.ReadOnlyFileSystemException
at com.sun.nio.zipfs.ZipFileSystem.checkWritable(ZipFileSystem.java:155)
at com.sun.nio.zipfs.ZipFileSystem.deleteFile(ZipFileSystem.java:1335)
at com.sun.nio.zipfs.ZipPath.delete(ZipPath.java:655)
at com.sun.nio.zipfs.ZipFileSystemProvider.delete(ZipFileSystemProvider.java:206)
at java.nio.file.Files.delete(Unknown Source)
at zipfs.ZipFS.main(ZipFS.java:23)
Am I doing something wrong? Is it impossible? Is there a workaround?
I ran into the same thing and I looked at the JDK code.
Findings
In ZipFileSystem.java there are three relevant lines:
zfpath.getFileSystem().provider().checkAccess(zfpath, AccessMode.READ);
if (!Files.isWritable(zfpath))
this.readOnly = true;
zfPath is a Path object. Something in the Windows FileSystem provider blocks write access to a zip archive path. Doesn't seem like there is much to be done about that.
Workaround
What I used as a workaround was to:
Create the zip archive in a temp folder
Populate the zip archive
Copy the temp file to the original mapped drive location
As long as the mapped drive is writable in contexts outside of the zip FileSystem, this method works.
We run in exactly the same problem, and have deviced somewhat barbarian solution.
FileSystems.newFileSystem(Path) accepts Path interface, so it's possible to satisfy it wrapping real Path instance.
Here is the solution that implements required FileSysteProvider.checkAccess():
private static FileSystem createZip(Path zipPath)
throws Exception
{
var fileSystem = zipPath.getFileSystem();
var provider = fileSystem.provider();
return FileSystems.newFileSystem(
new Path()
{
private Path path(Path path)
{
return this == path ? zipPath : path;
}
#Override
public FileSystem getFileSystem()
{
return new FileSystem()
{
public Set<String> supportedFileAttributeViews()
{
return fileSystem.supportedFileAttributeViews();
}
#Override
public FileSystemProvider provider()
{
return new FileSystemProvider()
{
#Override
public void setAttribute(
Path path,
String attribute,
Object value,
LinkOption... options)
throws IOException
{
provider.setAttribute(path(path), attribute, value, options);
}
#Override
public Map<String, Object> readAttributes(
Path path,
String attributes,
LinkOption... options)
throws IOException
{
return provider.
readAttributes(path(path), attributes, options);
}
#Override
public <A extends BasicFileAttributes> A readAttributes(
Path path,
Class<A> type,
LinkOption... options)
throws IOException
{
return provider.readAttributes(path(path), type, options);
}
#Override
public FileSystem newFileSystem(URI uri, Map<String, ?> env)
throws IOException
{
return provider.newFileSystem(uri, env);
}
#Override
public DirectoryStream<Path> newDirectoryStream(
Path dir,
Filter<? super Path> filter)
throws IOException
{
return provider.newDirectoryStream(dir, filter);
}
#Override
public SeekableByteChannel newByteChannel(
Path path,
Set<? extends OpenOption> options,
FileAttribute<?>... attrs)
throws IOException
{
return provider.newByteChannel(path(path), options, attrs);
}
#Override
public void move(
Path source,
Path target,
CopyOption... options)
throws IOException
{
provider.move(path(source), path(target), options);
}
#Override
public boolean isSameFile(Path path, Path path2)
throws IOException
{
return provider.isSameFile(path(path), path(path2));
}
#Override
public boolean isHidden(Path path)
throws IOException
{
return provider.isHidden(path(path));
}
#Override
public String getScheme()
{
return provider.getScheme();
}
#Override
public Path getPath(URI uri)
{
return provider.getPath(uri);
}
#Override
public FileSystem getFileSystem(URI uri)
{
return provider.getFileSystem(uri);
}
#Override
public FileStore getFileStore(Path path)
throws IOException
{
return provider.getFileStore(path(path));
}
#Override
public <V extends FileAttributeView> V getFileAttributeView(
Path path,
Class<V> type,
LinkOption... options)
{
return provider.
getFileAttributeView(path(path), type, options);
}
#Override
public void delete(Path path)
throws IOException
{
provider.delete(path(path));
}
#Override
public void createDirectory(Path dir, FileAttribute<?>... attrs)
throws IOException
{
provider.createDirectory(path(dir), attrs);
}
#Override
public void copy(
Path source,
Path target,
CopyOption... options)
throws IOException
{
provider.copy(path(source), path(target), options);
}
#Override
public void checkAccess(Path path, AccessMode... modes)
throws IOException
{
if ((modes != null) &&
(modes.length == 1) &&
(modes[0] == AccessMode.WRITE))
{
return;
}
provider.checkAccess(path(path), modes);
}
};
}
#Override
public WatchService newWatchService()
throws IOException
{
return fileSystem.newWatchService();
}
#Override
public boolean isReadOnly()
{
return false;
}
#Override
public boolean isOpen()
{
return fileSystem.isOpen();
}
#Override
public UserPrincipalLookupService getUserPrincipalLookupService()
{
return fileSystem.getUserPrincipalLookupService();
}
#Override
public String getSeparator()
{
return fileSystem.getSeparator();
}
#Override
public Iterable<Path> getRootDirectories()
{
return fileSystem.getRootDirectories();
}
#Override
public PathMatcher getPathMatcher(String syntaxAndPattern)
{
return fileSystem.getPathMatcher(syntaxAndPattern);
}
#Override
public Path getPath(String first, String... more)
{
return fileSystem.getPath(first, more);
}
#Override
public Iterable<FileStore> getFileStores()
{
return fileSystem.getFileStores();
}
#Override
public void close() throws IOException
{
fileSystem.close();
}
};
}
#Override
public boolean isAbsolute()
{
return zipPath.isAbsolute();
}
#Override
public Path getRoot()
{
return zipPath.getRoot();
}
#Override
public Path getFileName()
{
return zipPath.getFileName();
}
#Override
public Path getParent()
{
return zipPath.getParent();
}
#Override
public int getNameCount()
{
return zipPath.getNameCount();
}
#Override
public Path getName(int index)
{
return zipPath.getName(index);
}
#Override
public Path subpath(int beginIndex, int endIndex)
{
return zipPath.subpath(beginIndex, endIndex);
}
#Override
public boolean startsWith(Path other)
{
return zipPath.startsWith(other);
}
#Override
public boolean endsWith(Path other)
{
return zipPath.endsWith(other);
}
#Override
public Path normalize()
{
return zipPath.normalize();
}
#Override
public Path resolve(Path other)
{
return zipPath.relativize(other);
}
#Override
public Path relativize(Path other)
{
return zipPath.relativize(other);
}
#Override
public URI toUri()
{
return zipPath.toUri();
}
#Override
public Path toAbsolutePath()
{
return zipPath.toAbsolutePath();
}
#Override
public Path toRealPath(LinkOption... options)
throws IOException
{
return zipPath.toRealPath(options);
}
#Override
public WatchKey register(
WatchService watcher,
Kind<?>[] events,
Modifier... modifiers)
throws IOException
{
return zipPath.register(watcher, events, modifiers);
}
#Override
public int compareTo(Path other)
{
return zipPath.compareTo(other);
}
},
Map.of("create", "true"));
}
This is more like a hack but we think it solves the bug in original ZipFileSystem
Try setting the private field readOnly in ZipFileSystem to false with reflection.