I have a lot of resource files bundled with my Java app. These files have filenames containing international characters like ü or æ. I would like to load these files using getClass().getResource(), but apparently this is not supported since for these particular file names, the getResource method always returns null.
That made me experiment with using URL encoding of the international characters, but this is not supported either as stated by http://bugs.sun.com/bugdatabase/view_bug.do?bug_id=4968789.
So, my question is: What is the recommended way of loading a resource which has a name containing international characters? For example, I need to load the UTF-8 contents of a file named Sjælland.txt
Not sure if there is a best (it is probably a candidate for worst because it is quite a hack) but this seems like a capable mechanism. It sidesteps the need to use getResource by reading the jar directly.
public class NavelGazing {
public static void main(String[] args) throws Throwable {
// Do a little navel gazing.
java.net.URL codeBase = NavelGazing.class.getProtectionDomain().getCodeSource().getLocation();
// Must be a jar.
if (codeBase.getPath().endsWith(".jar")) {
// Open it.
java.util.jar.JarInputStream jin = new java.util.jar.JarInputStream(codeBase.openStream());
// Walk the entries.
ZipEntry entry;
while ((entry = jin.getNextEntry()) != null ) {
System.out.println("Entry: "+entry.getName());
}
}
}
}
I added a file called Sjælland.txt and this did successfully get the entry.
I am not sure that I understand you correctly, but if I try
URL url = Test.class.getResource("/Sjælland.txt");
Object o = url.getContent();
then o is a sun.net.www.content.text.PlainTextInputStream.
I'm using JDK 1.6 on a Windows machine. I've got (default?) System.property sun.jnu.encoding set to Cp1252. So it all seems to work fine. The bug you've posted seems to be JDK 1.4. It might be what you're using.
Related
I want to get a file from the resource file, and to use it in string.
I tried this :
ClassLoader classLoader = getClass().getClassLoader();
File file = new File(classLoader.getResource("/resources/fileC.p12").getFile());
String data = String.valueOf(file);
but doesnt work, thanks for helping
I tried this but i had a error, it returns a null value
new File In java, File means File. As in, an actual file on your actual harddisk. Resources aren't - they are entries in a jarfile and therefore not a file. Simply put, resources cannot be read this way.
Fortunately, File in general is barking up the wrong tree: The correct abstraction is InputStream or similar - that represents 'any stream of bytes'. A file can be an InputStream. So can a network socket, a blob from a network, or, indeed, a resource being streamed to you by the classloader, which could very well be getting it from a network or generating it whole cloth - classloaders can do that. It's an abstract mechanism.
You're also doing it wrong - you want Type.class.getResource. Your way is needlessly wordy and will fail in exotic scenarios (such as bootloaders and agents and the like, which have no classloader).
class Example {
public String getDataFromFileC() throws IOException {
try (var in = Example.class.getResourceAsStream("/resources/fileC.p12")) {
return new String(in.readAllBytes(), StandardCharsets.UTF_8);
}
}
}
This:
Uses getResourceAsStream which gives you an inputstream. As I mentioned, if you mention File, you lose. Hence, we don't.
Uses the proper form: MyType.class.get. This avoids issues when subclassing or in root classloader situations.
MyType.class.get needs a leading slash. the getResource on classloaders requires you not to have it (which explains why your snippet wouldn't work in any scenario - that leading slash).
Uses try-with-resources as you should.
Propagates exceptions as you should.
Configures charset which you should do anytime you go from bytes to strings or vice versa.
NB: Depending on your build system, it may package those resources in the jar as /fileC.p12 and not as /resources/fileC.p12 - in fact, that is likely. You may want to update this to "/fileC.p12".
NB2: String.valueOf(file); does not read files. It just calls toString() on the file object which gives you a path. Resources don't have to be a path so this cannot work. They do have a URL, which may or may not be useful. If you want that: return MyClass.class.getResource("/resources/fileC.p12").toString();.
String data = new String(getClass().getResourceAsStream("/fileC.p12").readAllBytes());
Your resource (which shouldn't be seen as a file as it could and probably should be packaged with your app) is at the root, so useful to start with '/' then it can be addressed from any package. Be cautious with Java >= 17 as that will be decoded by default as UTF-8, so if that's not the encoding, you will have to specify what is in the String ctor. It might be safer to do that anyway.
My CodenameOne app is being tested on the iOS simulator (iPad 8th iOS 14).
It writes some files in the private folder by means of this method:
public void writeFile() throws IOException {
try(OutputStream os = FileSystemStorage.getInstance().openOutputStream(Utils.getRootPath()+DATA_FILE);)
{
os.write(JSONText.getBytes("UTF-8"));
os.flush();
os.close();
} catch(IOException err) {
System.out.println("exception trying to write");
}
}
It works on the CN simulator (writes inside the .cn1/ folder)
but on iOS the exception is catched. The Library folder is of paramount importance on iOS.
Below is the method to get the root path
public static String getRootPath()
{
String documentsRoot=FileSystemStorage.getInstance().getRoots()[0];
String os=Display.getInstance().getPlatformName();
if (os.toLowerCase().contains("ios")) {
int pos=documentsRoot.lastIndexOf("Documents");
if (pos==-1) return documentsRoot+"/";
String libraryRoot=documentsRoot.substring(0,pos)+"Library";
String result=libraryRoot+"/";
return result;
}
The CN version of my app has to write those private files in the same location as the swift version, that is Library.
There is string manipulation, and no extra '/' are added, the file path seems legit.
So the string
file:///Users/mac/Library/Developer/CoreSimulator/Devices/alphanumeric-string/data/Containers/Data/Application/another-alphanumeric-string/Documents/
is transformed and
the getRootPath() method returns
file:///Users/mac/Library/Developer/CoreSimulator/Devices/alphanumeric-string/data/Containers/Data/Application/another-alphanumeric-string/Library/
But there is exception.
Furthermore, at some point after the writing attempt, I see in the console output something I think is relevant:
Failed to create directory /Users/mac/Library/Developer/CoreSimulator/Devices/alphanumeric-string/data/Containers/Data/Application/another-alphanumeric-string/Documents/cn1storage/
What is this? Is it related to my problem?
Is CN filesystem access broken or flawed?
I know io access permissions are automatically created by the CN compiler, but are they working?
So how to fix my issue about the Library folder?
The cn1storage printout just means the storage directory already exists.
The way to get the library path is this: Getting an iOS application's "~/Library" path reliably
You need to use that approach. I think your assumption that Document and Library reside under the exact same hierarchy is just incorrect.
I have a simple problem that I am quite struggling with. I have several files in a directory and I am reading them and passing processing them based on their type (extension). However, as an input, I receive a path to the file without extension so I have to identify the type myself.
example (files):
files/file1.txt
files/file1.txt
files/pic1.jpg
----------------
String path = "files/file1";
String ext = FilenameUtils.getExtension(path); // this returns null
Is there a way to identify the type of file when the extension is not included in the path?
Your best bet here is to "do it yourself" by implementing instances of FileTypeDetectors.
When you have this, you can then just use Files.probeContentType() to have a string returned which describes the file contents as a MIME type.
The JDK does provide a default implementation but it relies on file extensions, basically; if you have a PNG image named foo.txt, the default implementation will return text/plain where the file is really an image/png.
Which is of course wrong.
Final note: if all you really have is only part of the file name, then use Files.newDirectoryStream() and provide it with the appropriate DirectoryStream.Filter<Path>. Not sure yet why you only have part of it though.
Since you're only given part of the file name, you'll need to search for files that start with that prefix. Note that there could be multiple matches.
Using java.nio.file
Path prefix = Paths.get(path);
Path directory = prefix.getParent();
try (Stream<Path> stream = Files.list(directory)) {
stream.filter(p -> p.getFileName().startsWith(prefix.getFileName() + "."))
.forEach(p -> System.out.printf("Found %s%n", p));
}
Using java.io
File prefix = new File(path);
File directory = prefix.getParentFile();
List<File> matches = directory.listFiles((dir, name) ->
name.startsWith(prefix.getName() + "."));
for (File match: matches) {
System.out.printf("Found %s%n", match);
}
Files.probeContentType(Path) implements a basic MIME type inquiry you can use (or extend), the internal details of which are platform specific. You can also make a little utility method that walks a Set of extensions. A combination of the two approaches may be necessary, depending on your application.
The MIME type checker will give different results on different releases implementations of the JRE. So, always have a fail-over solution.
See: http://docs.oracle.com/javase/7/docs/api/java/nio/file/Files.html#probeContentType%28java.nio.file.Path
[EDIT]
This actually does not answer the question posited, as this method needs a full, legal Path object to work on. If you are given just the stem name, and the extension is missing, then you neither have an extension to work with nor a valid Path name for Files to work with [and probeContentType() may, in some implementations, just use the extension anyway.]
I'm not sure how you can do this without Path that refers to a real on-disk file that the JRE can access, or by hand if you don't have an extension. If you don't have a File of some sort, you can't even open it up yourself to attempt file type "magic".
I have an I18n helper class that can find out the available Locales by looking at the name of the files inside the application's Jar.
private static void addLocalesFromJar(List<Locale> locales) throws IOException {
ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();
JarInputStream jar = new JarInputStream(url.openStream());
while (true) {
JarEntry entry = jar.getNextJarEntry();
if (entry == null) {
break;
}
String name = entry.getName();
// ...
}
}
Currently, this isn't working - jar.getNextJarEntry() seems to always return null. I have no idea why that's happening, all I know is that url is set to rsrc:./. I have never seen that protocol, and couldn't find anything about it.
Curiously, this works:
class Main {
public static void main(String[] args) {
URL url = Main.class.getProtectionDomain().getCodeSource().getLocation();
JarInputStream jar = new JarInputStream(url.openStream());
while (true) {
JarEntry entry = jar.getNextJarEntry();
if (entry == null) {
break;
}
System.out.println(entry.getName());
}
}
}
In this version, even though there is practically no difference between them, the url is correctly set to the path of the Jar file.
Why doesn't the first version work, and what is breaking it?
UPDATE:
The working example really only works if I don't use Eclipse to export it. It worked just fine in NetBeans, but in the Eclipse version the URL got set to rsrc:./ too.
Since I exported it with Package required libraries into generated JAR library handling, Eclipse put its jarinjarloader in my Jar so I can have all dependencies inside it. It works fine with the other settings, but is there any way to make this work independently of them?
Another question
At the moment, that class is part of my application, but I plan to put it in a separate library. In that case, how can I make sure it will work with separate Jars?
The problem is the jarinjarloader ClassLoader that is being used by Eclipse. Apparently it is using its own custom rsrc: URL scheme to point to jar files stored inside the main jar file. This scheme is not understood by your URL stream handler factory, so the openStream() method returns null which causes the problem that you're seeing.
This answers the second part of your question about separate jars - not only will this work, it's the only way that it will work. You need to change your main application to use separate jars instead of bundling them all up inside the main jar. If you're building a web application, copy them into the WEB-INF/lib directory and you're fine. If you're building a desktop application, add a relative path reference in the META-INF/MANIFEST.MF to the other jars, and they will automatically be included as part of the classpath when you run the main jar.
The code may or may not result into the jar file where I18n resides. Also getProtectionDomain can be null. It depends how the classloader is implemented.
ProtectionDomain domain = I18n.class.getProtectionDomain();
CodeSource src = domain.getCodeSource();
URL url = src.getLocation();
about the rsrc:./ protocol, the classloader is free to use whatever URL they please (or name it for that matter)
try this out, you might get lucky :)
URL url = getClass().getResource(getClass().getSimpleName()+".class");
java.net.JarURLConnection conn = (java.net.JarURLConnection) url.openConnection();
Enumeration<JarEntry> e = conn.getJarFile().entries();
...
and good luck!
Eclipse's jarinjarloader loads everything using the system classloader and it never knows what jar file it was loaded from. That's why you can't get the jar URL for a rsrc: url.
I suggest storing the list of locales in a file in each application jar, e.g. META-INF/locales. Then you can use ClassLoader.getResources("META-INF/locales") to get the list of all the files with that name in the classpath and combine them to obtain the full list of locales.
I use System.getProperty("java.class.path") for getting the location of the jar. I do not know if that makes a difference. I have not explored the ProtectDomain path so I cannot help you there, sorry. As for multiple jars, just iterate through those jar file also.
I have developed a number of classes which manipulate files in Java. I am working on a Linux box, and have been blissfully typing new File("path/to/some/file");. When it came time to commit I realised some of the other developers on the project are using Windows. I would now like to call a method which can take in a String of the form "/path/to/some/file" and, depending on the OS, return a correctly separated path.
For example:
"path/to/some/file" becomes "path\\to\\some\\file" on Windows.
On Linux it just returns the given String.
I realise it wouldn't take long to knock up a regular expression that could do this, but I'm not looking to reinvent the wheel, and would prefer a properly tested solution. It would be nice if it was built in to the JDK, but if it's part of some small F/OSS library that's fine too.
So is there a Java utility which will convert a String path to use the correct File separator char?
Apache Commons comes to the rescue (again). The Commons IO method FilenameUtils.separatorsToSystem(String path) will do what you want.
Needless to say, Apache Commons IO will do a lot more besides and is worth looking at.
A "/path/to/some/file" actually works under Windows Vista and XP.
new java.io.File("/path/to/some/file").getAbsoluteFile()
> C:\path\to\some\file
But it is still not portable as Windows has multiple roots. So the root directory has to be selected in some way. There should be no problem with relative paths.
Edit:
Apache commons io does not help with envs other than unix & windows. Apache io source code:
public static String separatorsToSystem(String path) {
if (path == null) {
return null;
}
if (isSystemWindows()) {
return separatorsToWindows(path);
} else {
return separatorsToUnix(path);
}
}
This is what Apache commons-io does, unrolled into a couple of lines of code:
String separatorsToSystem(String res) {
if (res==null) return null;
if (File.separatorChar=='\\') {
// From Windows to Linux/Mac
return res.replace('/', File.separatorChar);
} else {
// From Linux/Mac to Windows
return res.replace('\\', File.separatorChar);
}
}
So if you want to avoid the extra dependency, just use that.
With the new Java 7 they have included a class called Paths this allows you to do exactly what you want (see http://docs.oracle.com/javase/tutorial/essential/io/pathOps.html)
here is an example:
String rootStorePath = Paths.get("c:/projects/mystuff/").toString();
Do you have the option of using
System.getProperty("file.separator")
to build the string that represents the path?
For anyone trying to do this 7 years later, the apache commons separatorsToSystem method has been moved to the FilenameUtils class:
FilenameUtils.separatorsToSystem(String path)
I create this function to check if a String contain a \ character then convert them to /
public static String toUrlPath(String path) {
return path.indexOf('\\') < 0 ? path : path.replace('\\', '/');
}
public static String toUrlPath(Path path) {
return toUrlPath(path.toString());
}
String fileName = Paths.get(fileName).toString();
Works perfectly with Windows at least even with mixed paths, for example
c:\users\username/myproject\myfiles/myfolder
becomes
c:\users\username\myproject\myfiles\myfolder
Sorry haven't check what Linux would make of the above but there again Linux file structure is different so you wouldn't search for such a directory
I think there is this hole in Java Paths.
String rootStorePath = Paths.get("c:/projects/mystuff/").toString();
works if you are running it on a system that has the file system you need to use. As pointed out, it used the current OS file system.
I need to work with paths between windows and linux, say to copy a file from one to another. While using "/" every works I guess if you are using all Java commands, but I need to make an sftp call so using / or file.separator etc... does not help me. I cannot use Path() because it converts mine to the default file system I am running on "now".
What Java needs is:
on windows system:
Path posixPath = Paths.get("/home/mystuff", FileSystem.Posix );
stays /home/mystuff/ and does not get converted to \\home\\mystuff
on linux system:
String winPath = Paths.get("c:\home\mystuff", FileSystem.Windows).toString();
stays c:\home\mystuff and does not get converted to /c:/home/mystuff
similar to working with character sets:
URLEncoder.encode( "whatever here", "UTF-8" ).getBytes();
P.S. I also do not want to load a whole apache io jar file to do something simple either. In this case they do not have what I propose anyways.
Shouldn't it be enough to say:
"path"+File.Seperator+"to"+File.Seperator+"some"+File.Seperator+"file"