Change the watcher service base on user action - java

I am developing a Java Watcher Service for the root directory and sub directory for any new folder and file creation. My root directory is C:/REST API/source/. Here are my folder structure that might look like and only the root directory was created by default and the sub directory was created by end user
C:/REST API/source/
- /source/new folder
-/source/new folder/new folder
-/source/new folder(2)
My program will register C:/REST API/source/ as a root directory, if it's detected there's a new folder created in C:/REST API/source/, it's will register the path which is C:/REST API/source/new folder. The same process being tested for /source/new folder/new folder and it's work well. But when I try to create new folder under /source/ directory which is my root directory again, I found out the path is not correct This is my compiler statement
1
2
3
a
b
C:\REST API\source
c
d
e
C:\REST API\source
4
5
6
7
8
9
New folder
C:\REST API\source\New folder
file:///C:/REST%20API/source/New%20folder/
10
C:\REST API\source\New folder
11
12
13
aa
bb
C:\REST API\source\New folder
cc
dd
ee
14
C:\REST API\source\New folder
The new file :C:\REST API\source\New folderEvent :ENTRY_CREATE
5
6
7
8
9
New folder
C:\REST API\source\New folder\New folder
file:///C:/REST%20API/source/New%20folder/New%20folder/
10
C:\REST API\source\New folder\New folder
11
12
13
aa
bb
C:\REST API\source\New folder\New folder
cc
dd
ee
14
C:\REST API\source\New folder\New folder
The new file :C:\REST API\source\New folder\New folderEvent :ENTRY_CREATE
5
6
7
8
9
New folder (2)
C:\REST API\source\New folder\New folder\New folder (2)
file:///C:/REST%20API/source/New%20folder/New%20folder/New%20folder%20(2)
10
C:\REST API\source\New folder\New folder\New folder (2)
11
The new file :C:\REST API\source\New folder\New folder\New folder (2)Event :ENTRY_CREATE
5
Will it able to register back the root directory or detect which directory have been access by the user ,so the watcher service will register again base on the current directory access by the user if the current directory have been created by the user previously. Because what I have found out it's cause by the path=child. If I didn't overwrite the child to path variable, I am not able to retrieve back the file path if I put the file into the monitored directory
public class fileStatus {
public static void main(String [] args) throws FileNotFoundException, IOException, JSONException, InterruptedException
{
try(WatchService svc = FileSystems.getDefault().newWatchService())
{
System.out.println("1");
Map<WatchKey, Path> keyMap = new HashMap<>();
System.out.println("2");
Path path = Paths.get("C:/REST API/source/");
System.out.println("3");
fileStatus.registerAll(path,keyMap,svc);
System.out.println(path);
System.out.println("4");
WatchKey wk ;
do
{
System.out.println("5");
wk = svc.take();
System.out.println("6");
for(WatchEvent<?> event : wk.pollEvents())
{
System.out.println("7");
WatchEvent.Kind<?> type = event.kind();
System.out.println("8");
Path fileName = (Path)event.context();
System.out.println("9");
System.out.println(fileName);
Path child = path.resolve(fileName);
URI uri = child.toUri();
System.out.println(child);
System.out.println(uri);
System.out.println("10");
Path newPath = Paths.get(uri);
System.out.println(newPath);
System.out.println("11");
if (Files.isDirectory(newPath, LinkOption.NOFOLLOW_LINKS))
{
System.out.println("12");
if(type == StandardWatchEventKinds.ENTRY_CREATE)
{
System.out.println("13");
register(newPath,keyMap,svc);
System.out.println("14");
System.out.println(newPath);
child=newPath;
}
}
System.out.println("\nThe new file :"+child+ "Event :" +type);
path = child ;
}
}while(wk.reset());
}
catch(IOException e)
{
e.printStackTrace();
}
}
private static Path register(Path newPath, Map<WatchKey, Path> keyMap, WatchService svc) throws IOException
{
System.out.println("aa");
Files.walkFileTree(newPath,new SimpleFileVisitor<Path>()
{
public FileVisitResult preVisitDirectory(Path newPath, BasicFileAttributes attrs) throws IOException
{
System.out.println("bb");
System.out.println(newPath);
if(attrs.isDirectory())
{
System.out.println("cc");
keyMap.put(newPath.register(svc, StandardWatchEventKinds.ENTRY_CREATE),newPath);
}
System.out.println("dd");
return FileVisitResult.CONTINUE;
}
});
System.out.println("ee");
return newPath;
}
private static Path registerAll(Path path, Map<WatchKey, Path> keyMap, WatchService svc) throws IOException
{
System.out.println("a");
Files.walkFileTree(path,new SimpleFileVisitor<Path>()
{
public FileVisitResult preVisitDirectory(Path path, BasicFileAttributes attrs) throws IOException
{
System.out.println("b");
System.out.println(path);
if(attrs.isDirectory())
{
System.out.println("c");
keyMap.put(path.register(svc, StandardWatchEventKinds.ENTRY_CREATE),path);
}
System.out.println("d");
return FileVisitResult.CONTINUE;
}
});
System.out.println("e");
return path;
}
}

The issue with the code is that path is set to child in the end of while loop. It means that if
Directory a/ is tracked by default, the path is set to a
Then directory a/b/ is created and path is set to a/b/
Then directory a/c/ is created. It returns c from (Path) event.context(). But it is matched against path a/b/ by the next code path.resolve(fileName). It gives you a/b/c/ instead of a/c
Unfortunately WatchService doesn't allow to get directory which populated the event. You can create separate WatchService for every existing/new directory, but this could be overkill.
I would propose to use special Sun's class ExtendedWatchEventModifier.FILE_TREE to track directory with all its sub-directories. In this case, if a is tracked and a/b/c is created, than the Path got from WatchEvent will be b/c and you'll be able to resolve it using the root path. Please, see the rough code below:
import com.sun.nio.file.ExtendedWatchEventModifier;
import org.json.JSONException;
import java.io.IOException;
import java.net.URI;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
public class fileStatus {
public static void main(String[] args) throws IOException, JSONException, InterruptedException {
try (WatchService svc = FileSystems.getDefault().newWatchService()) {
final Path path = Paths.get("C:/ADovzhenko/watch_dir");
registerAll(path, svc);
WatchKey wk;
do {
wk = svc.take();
for (WatchEvent<?> event : wk.pollEvents()) {
WatchEvent.Kind<?> type = event.kind();
Path child = path.resolve((Path) event.context());
URI uri = child.toUri();
System.out.println("Created: " + child);
}
} while (wk.reset());
}
}
private static Path registerAll(Path path, final WatchService svc) throws IOException {
//Register folder and its sub-folders
path.register(svc, new WatchEvent.Kind<?>[]{StandardWatchEventKinds.ENTRY_CREATE}, ExtendedWatchEventModifier.FILE_TREE);
//Print all existing directories
Files.walkFileTree(path, new SimpleFileVisitor<Path>() {
public FileVisitResult preVisitDirectory(final Path dir, BasicFileAttributes attrs) throws IOException {
if (attrs.isDirectory()) {
System.out.println("Existing: " + dir);
return FileVisitResult.CONTINUE;
}
//In case if print of non-directory is required
//System.out.println("Existing: " + dir);
return FileVisitResult.SKIP_SIBLINGS;
}
});
return path;
}
}

Related

How to rename the folders and subfolders including the files present in it recursively using Java NIO Files?

I cant able to rename the folder which contains a files or sub folders in it.
My Folder structure is
D:
root
popcorn-folder1
popcorn-subfolder1
popcorn-subfile1
popcorn-file1
popcorn-folder2
popcorn-subfolder2
popcorn-file2
My resulting Directory should be
D:
root
folder1
subfolder1
subfile1
file1
folder2
subfolder2
file2
My tried code is
package com.din.pach;
import java.io.File;
import java.io.IOException;
import java.nio.file.FileVisitResult;
import java.nio.file.FileVisitor;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.nio.file.StandardCopyOption;
import java.nio.file.attribute.BasicFileAttributes;
public class FileNio {
public static void main(String[] args) throws IOException {
Path sourcePath = Paths.get("D:\\root\\");
Files.walkFileTree(sourcePath, new FileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path dir, BasicFileAttributes attrs) throws IOException {
// System.out.println("pre visit dir:" + dir);
//rename(dir);
//renameFile(dir);
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
//System.out.println("visit file: " + file);
renameFile(file);
System.out.println("====================================================================");
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFileFailed(Path file, IOException exc) throws IOException {
// System.out.println("visit file failed: " + file);
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult postVisitDirectory(Path dir, IOException exc) throws IOException {
// System.out.println("post visit directory: " + dir);
renameDirectory(dir);
return FileVisitResult.CONTINUE;
}
});
}
public static void renameFile(Path file) throws IOException {
boolean isDirectory = Files.isDirectory(file);
boolean isWritable = Files.isWritable(file);
System.out.println("isDirectory-> "+isDirectory);
System.out.println("isWritable-> "+isWritable);
Path sourcePath = Paths.get(file.toString());
String origName = file.getFileName().toString();
String newName = origName.replaceAll("POPCORN-", "");
if (isWritable&&!isDirectory) {
System.out.println("fname-> "+origName);
/*get the path of the directory*/
String baseLoc = file.getParent().toString();
System.out.println("baseLoc-> "+baseLoc);
if (origName.contains("POPCORN-") /*|| origName.contains("#") || origName.contains("#")*/){
System.out.println("Orig name-> "+origName);
/*origName = origName.replaceAll("&", "_and_");
origName = origName.replaceAll("#", "_at_");*/
System.out.println("New Name-> "+newName);
String newLoc = baseLoc+File.separator+newName;//having "/" hardcoded is not cross-platform.
System.out.println("newLoc-> "+newLoc);
//File newFile = new File(newLoc);
Path destinationPath = Paths.get(newLoc);
Files.move(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING);
} else {
System.out.println("No write permission");
}
}else{
/*if(origName.contains("POPCORN-") || origName.contains("#") || origName.contains("#")){
Files.copy(sourcePath, sourcePath.resolveSibling(newName),StandardCopyOption.REPLACE_EXISTING);
}*/
}
}
public static void renameDirectory(Path file) throws IOException {
boolean isDirectory = Files.isDirectory(file);
boolean isWritable = Files.isWritable(file);
System.out.println("isDirectory-> "+isDirectory);
System.out.println("isWritable-> "+isWritable);
Path sourcePath = Paths.get(file.toString());
String origName = file.getFileName().toString();
String newName = origName.replaceAll("POPCORN-", "");
if (isWritable&&isDirectory) {
if(origName.contains("POPCORN-") /*|| origName.contains("#") || origName.contains("#")*/){
Files.move(sourcePath, sourcePath.resolveSibling(newName),StandardCopyOption.ATOMIC_MOVE);
}
} else {
System.out.println("No write permission");
}
}
}
In above code, I can rename the files successfully. But below exception is thrown
Exception in thread "main" java.nio.file.AccessDeniedException: D:\root\POPCORN-folder1 -> D:\root\folder1
at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsFileCopy.move(Unknown Source)
at sun.nio.fs.WindowsFileSystemProvider.move(Unknown Source)
at java.nio.file.Files.move(Unknown Source)
at com.din.pach.FileNio.renameDirectory(FileNio.java:121)
at com.din.pach.FileNio$1.postVisitDirectory(FileNio.java:45)
at com.din.pach.FileNio$1.postVisitDirectory(FileNio.java:1)
at java.nio.file.Files.walkFileTree(Unknown Source)
at java.nio.file.Files.walkFileTree(Unknown Source)
at com.din.pach.FileNio.main(FileNio.java:19)
the implementation is based on this article Java NIO Files
This is sequel of Renaming a folder name which has sub directories is not working using java File.rename.to()
EDIT: I have enabled the write permission for all the folders and files.
But there is no answer to it.
UPDATE: updated the Full console output
isDirectory-> false
isWritable-> true
fname-> popcorn-file1.txt
baseLoc-> D:\root\popcorn-folder1
Orig name-> popcorn-file1.txt
New Name-> file1.txt
newLoc-> D:\root\popcorn-folder1\file1.txt
====================================================================
isDirectory-> false
isWritable-> true
fname-> popcorn-subfile1.txt
baseLoc-> D:\root\popcorn-folder1\popcorn-subfolder1
Orig name-> popcorn-subfile1.txt
New Name-> subfile1.txt
newLoc-> D:\root\popcorn-folder1\popcorn-subfolder1\subfile1.txt
====================================================================
isDirectory-> true
isWritable-> true
isDirectory-> true
isWritable-> true
Exception in thread "main" java.nio.file.AccessDeniedException: D:\root\popcorn-folder1 -> D:\root\folder1
at sun.nio.fs.WindowsException.translateToIOException(Unknown Source)
at sun.nio.fs.WindowsException.rethrowAsIOException(Unknown Source)
at sun.nio.fs.WindowsFileCopy.move(Unknown Source)
at sun.nio.fs.WindowsFileSystemProvider.move(Unknown Source)
at java.nio.file.Files.move(Unknown Source)
at com.din.pach.FileNio.renameDirectory(FileNio.java:121)
at com.din.pach.FileNio$1.postVisitDirectory(FileNio.java:45)
at com.din.pach.FileNio$1.postVisitDirectory(FileNio.java:1)
at java.nio.file.Files.walkFileTree(Unknown Source)
at java.nio.file.Files.walkFileTree(Unknown Source)
at com.din.pach.FileNio.main(FileNio.java:19)
As mentioned in the java docs of Java nio library -
java.nio.file.AccessDeniedException is a Checked exception thrown when a file system operation is denied, typically due to a file permission or other access check.
Your first step to solve this problem is to check if you have write permissions to the whole directory you are trying to change his name.
Unfortunately the real source of this problem is hard to detect, since your question only contains heavy modified and trimmed sample data.
Actually you tried to assign the same name too two or more folders. (e.g. "folder1" and "folder2" both should be renamed to "directory").
Since the CopyOption StandardCopyOption.ATOMIC_MOVE is set you only get the misleading java.nio.file.AccessDeniedException instead of FileAlreadyExistsException¹ or DirectoryNotEmptyException².
Now to avoid this you need to check if the folder already exists:
public static void renameDirectory(Path file) throws IOException {
boolean isDirectory = Files.isDirectory(file);
if(isDirectory) {
Path sourcePath = Paths.get(file.toString());
String origName = file.getFileName().toString();
if(origName.contains("POPCORN-")) {
String newName = origName.replaceAll("POPCORN-", "");
Path destinationPath = sourcePath.resolveSibling(newName);
// Check if the folder doesn't exists
if(!Files.exists()) {
// You can rename the folder without any difficulty
Files.move(sourcePath, destinationPath, StandardCopyOption.ATOMIC_MOVE);
} else {
// Print warning
System.err.println("Directory already exists! Try to rename \""+sourcePath+"\" to \""+destinationPath+"\"");
// Here you could add some code to propably handel this case. e.g. merge the folders or create another name
}
}
}
}
¹: FileAlreadyExistsException would be thrown when you use Files.move(sourcePath, destinationPath) and the destination already exists.
²: DirectoryNotEmptyException would be thrown when you use Files.move(sourcePath, destinationPath, StandardCopyOption.REPLACE_EXISTING) and the folder at the destination contains a file or folder.

SWT file drop: identify folders

Code
I'm trying to add drop functionality to my program so users can drag and drop files from Windows. Therefore I implemented the SWT DropTargetListener interface and added a SWT DropTarget to my SWT control.
public class MyFileDrop implements DropTargetListener {
public MyFileDrop(final Control control) {
addDropTarget(control);
}
private void addDropTarget(final Control control) {
final DropTarget dropTarget = new DropTarget(control, DND.DROP_COPY | DND.DROP_DEFAULT);
dropTarget.setTransfer(new Transfer[] { FileTransfer.getInstance() });
dropTarget.addDropListener(this);
}
#Override
public void drop(final DropTargetEvent event) {
String[] fileNames = (String[]) event.data;
for (final String fileName : fileNames) {
System.out.println(fileName);
}
}
// other DropTargetListener methods [...]
}
If I drag and drop a folder from Windows Explorer to my control, the folder name gets printed out. But all files inside that dropped folder aren't listed.
Example
folder1
- subfile1.png
- subfile2.exe
file1.png
file2.exe
If I select folder1, file1 and file2 and drag and drop it to my program, this is the output:
path/to/folder1
path/to/file1.png
path/to/file2.exe
Questions
Why aren't the files inside the folder dropped to the program? How can I archive that these files are also dropped?
How can I set the files apart from the folder?
Regards winklerrr
Code
So this is my solution
#Override
public void drop(DropTargetEvent event) {
String[] fileNames = (String[]) event.data;
for (String fileName : fileNames) {
final File file = new File(fileName);
if (file.isDirectory()) {
Collection<File> subFiles = FileUtils.listFiles(file, TrueFileFilter.INSTANCE, TrueFileFilter.INSTANCE);
for (File subFile : subFiles) {
System.out.println(subFile.getAbsolutePath());
}
} else {
System.out.println(file.getAbsolutePath());
}
}
}
I used the FileUtils from the commons-io package.
Example
folder1
- subfile1.png
- subfile2.exe
file1.png
file2.exe
With the new code, this is now the output:
path/to/folder1/subfile1.png
path/to/folder1/subfile2.exe
path/to/file1.png
path/to/file2.exe
Answers
How can I archive that these files are also dropped?
FileUtils.listFiles(File, IOFileFilter, IOFileFilter)
How can I set the files apart from the folder?
File.isDirectory()

Glob pattern to search for the directories,files inside a directory

I am using a glob pattern to recursively walk through through a directory and get all the files and directories inside that directory.
I have a directory : d:/test/newfolder/...
Inside the newfolder there are some files and some directories which internally contain some files.
For Example: d:/test/newfolder/new.txt , d:/test/newfolder/temp/tempfile.doc etc..
I am using Path Mathcher to match the globpatterns.
Here is my code:
PathMatcher matcher = FileSystems.getDefault().getPathMatcher("glob:" + pattern);
try {
Files.walkFileTree(startDir, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult preVisitDirectory(Path dir,BasicFileAttributes attrs) throws IOException {
if (matcher.matches(file.getFileName())) {
System.out.println("found files&directories: " + file.getFileName());
return FileVisitResult.CONTINUE;
}
#Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
if (matcher.matches(file.getFileName())) {
System.out.println("found files: " + file.getFileName());
}
return FileVisitResult.CONTINUE;
}
});
} catch (IOException e) {
e.printStackTrace();
}
If I want to search for a file, i can give the pattern glob:*.txt and it works fine.
But I do not know what pattern should I give to search for directory. I want to get all the files and directories present in "newfolder" directory.
I am trying with glob:newfolder/** , but It is not working for me.
And Is there anything wrong with the code?
Please help me.
Thanks in Advance

COPY_ATTRIBUTES not working with Zip filesystem

The Zip filesystem doesn't copy file attributes when using the Java nio Files.copy method with StandardCopyOption.COPY_ATTRIBUTES. Is it supposed to?
The following fully working example code demonstrates the issue. It copies two files into a zip file: one normal, the other read-only. If you then list the zip file (e.g. using 7-zip) you'll see they are both normal, not read-only.
public static void main(String[] args) throws Exception {
Path tmpdir = Files.createTempDirectory(null);
createFiles(tmpdir);
createZip(tmpdir);
}
private static void createFiles(Path tmpdir) throws IOException {
Files.write(tmpdir.resolve("a.txt"), Collections.singleton("Hello, world! (a)"));
Files.write(tmpdir.resolve("b.txt"), Collections.singleton("Hello, world! (b)"));
Files.setAttribute(tmpdir.resolve("b.txt"), "dos:readonly", true);
}
private static void createZip(Path dir) throws IOException
{
Path zip = dir.resolve("data.zip");
URI uri = URI.create("jar:" + zip.toUri());
try (FileSystem fs = FileSystems.newFileSystem(uri, Collections.singletonMap("create", "true"))) {
for (Path path : Files.newDirectoryStream(dir))
if (!path.equals(zip)) {
String name = path.getFileName().toString();
Files.copy(path, fs.getPath(name), StandardCopyOption.COPY_ATTRIBUTES);
}
}
}

How to list the files inside a JAR file?

I have this code which reads all the files from a directory.
File textFolder = new File("text_directory");
File [] texFiles = textFolder.listFiles( new FileFilter() {
public boolean accept( File file ) {
return file.getName().endsWith(".txt");
}
});
It works great. It fills the array with all the files that end with ".txt" from directory "text_directory".
How can I read the contents of a directory in a similar fashion within a JAR file?
So what I really want to do is, to list all the images inside my JAR file, so I can load them with:
ImageIO.read(this.getClass().getResource("CompanyLogo.png"));
(That one works because the "CompanyLogo" is "hardcoded" but the number of images inside the JAR file could be from 10 to 200 variable length.)
EDIT
So I guess my main problem would be: How to know the name of the JAR file where my main class lives?
Granted I could read it using java.util.Zip.
My Structure is like this:
They are like:
my.jar!/Main.class
my.jar!/Aux.class
my.jar!/Other.class
my.jar!/images/image01.png
my.jar!/images/image02a.png
my.jar!/images/imwge034.png
my.jar!/images/imagAe01q.png
my.jar!/META-INF/manifest
Right now I'm able to load for instance "images/image01.png" using:
ImageIO.read(this.getClass().getResource("images/image01.png));
But only because I know the file name, for the rest I have to load them dynamically.
CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
if (src != null) {
URL jar = src.getLocation();
ZipInputStream zip = new ZipInputStream(jar.openStream());
while(true) {
ZipEntry e = zip.getNextEntry();
if (e == null)
break;
String name = e.getName();
if (name.startsWith("path/to/your/dir/")) {
/* Do something with this entry. */
...
}
}
}
else {
/* Fail... */
}
Note that in Java 7, you can create a FileSystem from the JAR (zip) file, and then use NIO's directory walking and filtering mechanisms to search through it. This would make it easier to write code that handles JARs and "exploded" directories.
Code that works for both IDE's and .jar files:
import java.io.*;
import java.net.*;
import java.nio.file.*;
import java.util.*;
import java.util.stream.*;
public class ResourceWalker {
public static void main(String[] args) throws URISyntaxException, IOException {
URI uri = ResourceWalker.class.getResource("/resources").toURI();
Path myPath;
if (uri.getScheme().equals("jar")) {
FileSystem fileSystem = FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap());
myPath = fileSystem.getPath("/resources");
} else {
myPath = Paths.get(uri);
}
Stream<Path> walk = Files.walk(myPath, 1);
for (Iterator<Path> it = walk.iterator(); it.hasNext();){
System.out.println(it.next());
}
}
}
erickson's answer worked perfectly:
Here's the working code.
CodeSource src = MyClass.class.getProtectionDomain().getCodeSource();
List<String> list = new ArrayList<String>();
if( src != null ) {
URL jar = src.getLocation();
ZipInputStream zip = new ZipInputStream( jar.openStream());
ZipEntry ze = null;
while( ( ze = zip.getNextEntry() ) != null ) {
String entryName = ze.getName();
if( entryName.startsWith("images") && entryName.endsWith(".png") ) {
list.add( entryName );
}
}
}
webimages = list.toArray( new String[ list.size() ] );
And I have just modify my load method from this:
File[] webimages = ...
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex].getName() ));
To this:
String [] webimages = ...
BufferedImage image = ImageIO.read(this.getClass().getResource(webimages[nextIndex]));
I would like to expand on acheron55's answer, since it is a very non-safe solution, for several reasons:
It doesn't close the FileSystem object.
It doesn't check if the FileSystem object already exists.
It isn't thread-safe.
This is somewhat a safer solution:
private static ConcurrentMap<String, Object> locks = new ConcurrentHashMap<>();
public void walk(String path) throws Exception {
URI uri = getClass().getResource(path).toURI();
if ("jar".equals(uri.getScheme()) {
safeWalkJar(path, uri);
} else {
Files.walk(Paths.get(path));
}
}
private void safeWalkJar(String path, URI uri) throws Exception {
synchronized (getLock(uri)) {
// this'll close the FileSystem object at the end
try (FileSystem fs = getFileSystem(uri)) {
Files.walk(fs.getPath(path));
}
}
}
private Object getLock(URI uri) {
String fileName = parseFileName(uri);
locks.computeIfAbsent(fileName, s -> new Object());
return locks.get(fileName);
}
private String parseFileName(URI uri) {
String schemeSpecificPart = uri.getSchemeSpecificPart();
return schemeSpecificPart.substring(0, schemeSpecificPart.indexOf("!"));
}
private FileSystem getFileSystem(URI uri) throws IOException {
try {
return FileSystems.getFileSystem(uri);
} catch (FileSystemNotFoundException e) {
return FileSystems.newFileSystem(uri, Collections.<String, String>emptyMap());
}
}
There's no real need to synchronize over the file name; one could simply synchronize on the same object every time (or make the method synchronized), it's purely an optimization.
I would say that this is still a problematic solution, since there might be other parts in the code that use the FileSystem interface over the same files, and it could interfere with them (even in a single threaded application).
Also, it doesn't check for nulls (for instance, on getClass().getResource().
This particular Java NIO interface is kind of horrible, since it introduces a global/singleton non thread-safe resource, and its documentation is extremely vague (a lot of unknowns due to provider specific implementations). Results may vary for other FileSystem providers (not JAR). Maybe there's a good reason for it being that way; I don't know, I haven't researched the implementations.
So I guess my main problem would be, how to know the name of the jar where my main class lives.
Assuming that your project is packed in a Jar (not necessarily true!), you can use ClassLoader.getResource() or findResource() with the class name (followed by .class) to get the jar that contains a given class. You'll have to parse the jar name from the URL that gets returned (not that tough), which I will leave as an exercise for the reader :-)
Be sure to test for the case where the class is not part of a jar.
I've ported acheron55's answer to Java 7 and closed the FileSystem object. This code works in IDE's, in jar files and in a jar inside a war on Tomcat 7; but note that it does not work in a jar inside a war on JBoss 7 (it gives FileSystemNotFoundException: Provider "vfs" not installed, see also this post). Furthermore, like the original code, it is not thread safe, as suggested by errr. For these reasons I have abandoned this solution; however, if you can accept these issues, here is my ready-made code:
import java.io.IOException;
import java.net.*;
import java.nio.file.*;
import java.nio.file.attribute.BasicFileAttributes;
import java.util.Collections;
public class ResourceWalker {
public static void main(String[] args) throws URISyntaxException, IOException {
URI uri = ResourceWalker.class.getResource("/resources").toURI();
System.out.println("Starting from: " + uri);
try (FileSystem fileSystem = (uri.getScheme().equals("jar") ? FileSystems.newFileSystem(uri, Collections.<String, Object>emptyMap()) : null)) {
Path myPath = Paths.get(uri);
Files.walkFileTree(myPath, new SimpleFileVisitor<Path>() {
#Override
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
System.out.println(file);
return FileVisitResult.CONTINUE;
}
});
}
}
}
Here is an example of using Reflections library to recursively scan classpath by regex name pattern augmented with a couple of Guava perks to to fetch resources contents:
Reflections reflections = new Reflections("com.example.package", new ResourcesScanner());
Set<String> paths = reflections.getResources(Pattern.compile(".*\\.template$"));
Map<String, String> templates = new LinkedHashMap<>();
for (String path : paths) {
log.info("Found " + path);
String templateName = Files.getNameWithoutExtension(path);
URL resource = getClass().getClassLoader().getResource(path);
String text = Resources.toString(resource, StandardCharsets.UTF_8);
templates.put(templateName, text);
}
This works with both jars and exploded classes.
Here's a method I wrote for a "run all JUnits under a package". You should be able to adapt it to your needs.
private static void findClassesInJar(List<String> classFiles, String path) throws IOException {
final String[] parts = path.split("\\Q.jar\\\\E");
if (parts.length == 2) {
String jarFilename = parts[0] + ".jar";
String relativePath = parts[1].replace(File.separatorChar, '/');
JarFile jarFile = new JarFile(jarFilename);
final Enumeration<JarEntry> entries = jarFile.entries();
while (entries.hasMoreElements()) {
final JarEntry entry = entries.nextElement();
final String entryName = entry.getName();
if (entryName.startsWith(relativePath)) {
classFiles.add(entryName.replace('/', File.separatorChar));
}
}
}
}
Edit:
Ah, in that case, you might want this snippet as well (same use case :) )
private static File findClassesDir(Class<?> clazz) {
try {
String path = clazz.getProtectionDomain().getCodeSource().getLocation().getFile();
final String codeSourcePath = URLDecoder.decode(path, "UTF-8");
final String thisClassPath = new File(codeSourcePath, clazz.getPackage().getName().repalce('.', File.separatorChar));
} catch (UnsupportedEncodingException e) {
throw new AssertionError("impossible", e);
}
}
Just to mention that if you are already using Spring, you can take advantage of the PathMatchingResourcePatternResolver.
For instance to get all the PNG files from a images folder in resources
ClassLoader cl = this.getClass().getClassLoader();
ResourcePatternResolver resolver = new PathMatchingResourcePatternResolver(cl);
Resource[] resources = resolver.getResources("images/*.png");
for (Resource r: resources){
logger.info(r.getFilename());
// From your example
// ImageIO.read(cl.getResource("images/" + r.getFilename()));
}
A jar file is just a zip file with a structured manifest. You can open the jar file with the usual java zip tools and scan the file contents that way, inflate streams, etc. Then use that in a getResourceAsStream call, and it should be all hunky dory.
EDIT / after clarification
It took me a minute to remember all the bits and pieces and I'm sure there are cleaner ways to do it, but I wanted to see that I wasn't crazy. In my project image.jpg is a file in some part of the main jar file. I get the class loader of the main class (SomeClass is the entry point) and use it to discover the image.jpg resource. Then some stream magic to get it into this ImageInputStream thing and everything is fine.
InputStream inputStream = SomeClass.class.getClassLoader().getResourceAsStream("image.jpg");
JPEGImageReaderSpi imageReaderSpi = new JPEGImageReaderSpi();
ImageReader ir = imageReaderSpi.createReaderInstance();
ImageInputStream iis = new MemoryCacheImageInputStream(inputStream);
ir.setInput(iis);
....
ir.read(0); //will hand us a buffered image
Given an actual JAR file, you can list the contents using JarFile.entries(). You will need to know the location of the JAR file though - you can't just ask the classloader to list everything it could get at.
You should be able to work out the location of the JAR file based on the URL returned from ThisClassName.class.getResource("ThisClassName.class"), but it may be a tiny bit fiddly.
Some time ago I made a function that gets classess from inside JAR:
public static Class[] getClasses(String packageName)
throws ClassNotFoundException{
ArrayList<Class> classes = new ArrayList<Class> ();
packageName = packageName.replaceAll("\\." , "/");
File f = new File(jarName);
if(f.exists()){
try{
JarInputStream jarFile = new JarInputStream(
new FileInputStream (jarName));
JarEntry jarEntry;
while(true) {
jarEntry=jarFile.getNextJarEntry ();
if(jarEntry == null){
break;
}
if((jarEntry.getName ().startsWith (packageName)) &&
(jarEntry.getName ().endsWith (".class")) ) {
classes.add(Class.forName(jarEntry.getName().
replaceAll("/", "\\.").
substring(0, jarEntry.getName().length() - 6)));
}
}
}
catch( Exception e){
e.printStackTrace ();
}
Class[] classesA = new Class[classes.size()];
classes.toArray(classesA);
return classesA;
}else
return null;
}
public static ArrayList<String> listItems(String path) throws Exception{
InputStream in = ClassLoader.getSystemClassLoader().getResourceAsStream(path);
byte[] b = new byte[in.available()];
in.read(b);
String data = new String(b);
String[] s = data.split("\n");
List<String> a = Arrays.asList(s);
ArrayList<String> m = new ArrayList<>(a);
return m;
}
There are two very useful utilities both called JarScan:
www.inetfeedback.com/jarscan
jarscan.dev.java.net
See also this question: JarScan, scan all JAR files in all subfolders for specific class
The most robust mechanism for listing all resources in the classpath is currently to use this pattern with ClassGraph, because it handles the widest possible array of classpath specification mechanisms, including the new JPMS module system. (I am the author of ClassGraph.)
How to know the name of the JAR file where my main class lives?
URI mainClasspathElementURI;
try (ScanResult scanResult = new ClassGraph().whitelistPackages("x.y.z")
.enableClassInfo().scan()) {
mainClasspathElementURI =
scanResult.getClassInfo("x.y.z.MainClass").getClasspathElementURI();
}
How can I read the contents of a directory in a similar fashion within a JAR file?
List<String> classpathElementResourcePaths;
try (ScanResult scanResult = new ClassGraph().overrideClasspath(mainClasspathElementURI)
.scan()) {
classpathElementResourcePaths = scanResult.getAllResources().getPaths();
}
There are lots of other ways to deal with resources too.
One more for the road that's a bit more flexible for matching specific filenames because it uses wildcard globbing. In a functional style this could resemble:
import java.io.IOException;
import java.net.URISyntaxException;
import java.nio.file.FileSystem;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.function.Consumer;
import static java.nio.file.FileSystems.getDefault;
import static java.nio.file.FileSystems.newFileSystem;
import static java.util.Collections.emptyMap;
/**
* Responsible for finding file resources.
*/
public class ResourceWalker {
/**
* Globbing pattern to match font names.
*/
public static final String GLOB_FONTS = "**.{ttf,otf}";
/**
* #param directory The root directory to scan for files matching the glob.
* #param c The consumer function to call for each matching path
* found.
* #throws URISyntaxException Could not convert the resource to a URI.
* #throws IOException Could not walk the tree.
*/
public static void walk(
final String directory, final String glob, final Consumer<Path> c )
throws URISyntaxException, IOException {
final var resource = ResourceWalker.class.getResource( directory );
final var matcher = getDefault().getPathMatcher( "glob:" + glob );
if( resource != null ) {
final var uri = resource.toURI();
final Path path;
FileSystem fs = null;
if( "jar".equals( uri.getScheme() ) ) {
fs = newFileSystem( uri, emptyMap() );
path = fs.getPath( directory );
}
else {
path = Paths.get( uri );
}
try( final var walk = Files.walk( path, 10 ) ) {
for( final var it = walk.iterator(); it.hasNext(); ) {
final Path p = it.next();
if( matcher.matches( p ) ) {
c.accept( p );
}
}
} finally {
if( fs != null ) { fs.close(); }
}
}
}
}
Consider parameterizing the file extensions, left an exercise for the reader.
Be careful with Files.walk. According to the documentation:
This method must be used within a try-with-resources statement or similar control structure to ensure that the stream's open directories are closed promptly after the stream's operations have completed.
Likewise, newFileSystem must be closed, but not before the walker has had a chance to visit the file system paths.
Just a different way of listing/reading files from a jar URL and it does it recursively for nested jars
https://gist.github.com/trung/2cd90faab7f75b3bcbaa
URL urlResource = Thead.currentThread().getContextClassLoader().getResource("foo");
JarReader.read(urlResource, new InputStreamCallback() {
#Override
public void onFile(String name, InputStream is) throws IOException {
// got file name and content stream
}
});

Categories