In JRE-9/lib directory (at least on Windows), there is a new file called modules whose size is about 107 MB. Is it possible to extract that file or maybe list java modules within it?
I can see that a new tool called jmod is available at jdk-9/bin/jmod.exe, but that is for reading .jmod files which is located at jdk-9/jmods and it cannot read the file modules.
The modules file is a container file. It's internal to the JDK and the format is not documented (it may change at any time). For troubleshooting purposes, the jimage tool in the bin directory can be used to list or extract the contents.
The runtime resources are handled in a backwards compatible way. E.g. when you did
URL url = Object.class.getResource("Object.class");
System.out.println(url);
in the past, you usually got something like
jar:file:/path-to-jre/lib/rt.jar!/java/lang/Object.class
Running the same under Java 9 will give you
jrt:/java.base/java/lang/Object.class
instead. In either case, you can open a FileSystem on it to inspect the other available resources (since Java 7). While the ZipFileSystem had to be created via FileSystems.newFileSystem first, Java 9’s file system is even already open for use:
private static void readMyOwnJRE() throws IOException {
try {
Path p = Paths.get(URI.create("jrt:/")).resolve("/modules");
System.out.println("My own JRE's modules:");
Files.list(p).forEach(m -> System.out.println(m.getFileName()));
System.out.println();
} catch(FileSystemNotFoundException ex) {
System.out.println("Could not read my modules (perhaps not Java 9?).");
}
}
If you are running under a different JRE than the one you want to inspect, you have to load the appropriate file system implementation manually first, but this opens the possibility to inspect a Java 9 installation even from a Java 8 JRE:
public static void readOtherJRE(Path pathToJRE) throws IOException {
Path p = pathToJRE.resolve("lib").resolve("jrt-fs.jar");
if(Files.exists(p)) {
try(URLClassLoader loader = new URLClassLoader(new URL[]{ p.toUri().toURL() });
FileSystem fs = FileSystems.newFileSystem(URI.create("jrt:/"),
Collections.emptyMap(),
loader)) {
System.out.println("Modules of "+pathToJRE);
Files.list(fs.getPath("/modules")).forEach(System.out::println);
System.out.println();
}
}
}
Once you have a file system (or a Path into it), you can use all standard functions of, e.g. Files to inspect or extract/copy the data, though the right term would be “to store an equivalent class file” in a different file system, as the runtime image’s representation doesn’t have to be a class file at all.
The modules file is meant to be a single file (not meant to be extracted anywhere) which contains a binary representation of all of the modules present in the JDK in an undocument format which is subject to change. You can list the modules it contains with java --list-modules.
Initially the modules file will contain every module and basically double the side of the JDK on its own, but once you "minify" your JDK using the jlink utility, the modules file will become smaller (assuming your program contains a subset of the modules provided with the JDK). For more info on jlink see here: http://openjdk.java.net/jeps/282
you can use
$ jimage list $JAVA_HOME/lib/modules
to list all the system packages' name,like "java/io/EOFException.class",etc.
also,you can use
$ jimage extract --dir=<directory> $JAVA_HOME/lib/modules
to extract all the .class files into the specified directory.
reference:https://adoptopenjdk.gitbooks.io/adoptopenjdk-getting-started-kit/en/intermediate-steps/openjdk9-jimage.html
You can list modules via java --list-modules
You can use libjimage to read this file.
Related
Asked this question, having already tried possible solutions in other questions here on stack but that didn't allow me to fix the problem.
As in the title, I have created a java utility with which I have to perform operations on text files, in particular I have to perform simple operations to move between directories, copy from one directory to another, etc.
To do this I have used the java libraries java.io.File and java.nio.*, And I have implemented two functions for now,copyFile(sourcePath, targetPath) and moveFile(sourcePath, targetPath).
To develop this I am using a mac, and the files are under the source path /Users/myname/Documents/myfolder/F24/archive/, and my target path is /Users/myname/Documents/myfolder/F24/target/.
But when I run my code I get a java.nio.file.NoSuchFileException: /Users/myname/Documents/myfolder/F24/archive
Having tried the other solutions here on stack and java documentation already I haven't been able to fix this yet ... I accept any advice or suggestion
Thank you all
my code:
// copyFile: funzione chiamata per copiare file
public static boolean copyFile(String sourcePath, String targetPath){
boolean fileCopied = true;
try{
Files.copy(Paths.get(sourcePath), Paths.get(targetPath), StandardCopyOption.REPLACE_EXISTING);
}catch(Exception e){
String sp = Paths.get(sourcePath)+"/";
fileCopied = false;
System.out.println("Non posso copiare i file dalla cartella "+sp+" nella cartella "+Paths.get(targetPath)+" ! \n");
e.printStackTrace();
}
return fileCopied;
}
Files.copy cannot copy entire directories. The first 'path' you pass to Files.copy must ALL:
Exist.
Be readable by the process that runs the JVM. This is non-trivial on a mac, which denies pretty much all disk rights to all apps by default until you give it access. This can be tricky for java apps. I'm not quite sure how you fix it (I did something on my mac to get rid of that, but I can't remember what - possibly out of the box java apps just get to read whatever they want and it's only actual mac apps that get pseudo-sandboxed. Point is, there's a chance it's mac's app access control denying it even if the unix file rights on this thing indicate you ought to be able to read it).
Be a plain old file and not a directory or whatnot.
Files.move can (usually - depends on impl and underlying OS) usually be done to directories, but not Files.copy. You're in a programming language, not a shell. If you want to copy entire directories, write code that does this.
Not sure whether my comment is understood though answered.
Ìn java SE target must not be the target directory. In other APIs of file copying
one can say COPY FILE TO DIRECTORY. In java not so; this was intentionally designed to remove one error cause.
That style would be:
Path source = Paths.get(sourcePath);
if (Files.isRegularFile(source)) {
Path target = Paths.get(targetPath);
Files.createDirectories(target);
if (Files.isDirectory(target)) {
target = Paths.get(targetPath, source.getFileName().toString());
// Or: target = target.resolve(source.getFileName().toString());
}
Files.copy(source, target, StandardCopyOption.REPLACE_EXISTING);
}
Better ensure when calling to use the full path.
So I have a project, and this is one of the demands:
You should have a class named Project3, containing a main method.
This program reads the levels information from a file whose name is
specified as a command-line parameter (The file should also be
relative to the class-path as described here:)
All the file names specified in the levels and block definition files
should be relative to the class path. The reason we want them to be
relative to the class path is that later we will be able to read the
files from inside a jar, something we can not do with regular File
references.
To get an input stream relative to the class path (even if it's inside
a jar), use the following:
InputStream is =
ClassLoader.getSystemClassLoader().getResourceAsStream("image.png");
The idea is to keep a folder with files(definitions and images) and
then add that folder to the class path when running the JVM:
java -cp bin:resources ... If you don't add the resources folder to
you class path you wont be able to load them with the command from
above.
When run without parameters, your program should read a default level
file, and run the game accordingly. The location of the default level
file should be hard-coded in your code, and be relative to the
classpath_.
When run without parameters, your program should read a default level file, and run the game accordingly. The location of the default level file should be hard-coded in your code, and be relative to the classpath_.
The part of the code that handles the input is:
public Void run() throws IOException {
LevelReader level = new LevelReader();
List<level> chosenLevels = new ArrayList<>();
if (args.length >= 1) {
File f = new File(args[0]);
if (f.exists()) {
chosenLevels = level.makeLevel(args[0]);
}
}
if (chosenLevels.size() == 0) {
game.runLevels(defaultLevels);
} else {
game.runLevels(chosenLevels);
}
return null;
}
So my question is:
An argument should be the full path of a file which means:
D:\desktop\level3.txt
Is it possible to read a file from every location on my computer?
Because right now I can do it only if my text file is in the
project's directory (not even in the src folder).
I can't understand the rest of their demands. What does is mean "should be hard-coded in your code, and be relative to the
classpath_." and why is it related to InputStream method(?)
I'm confused all over this.
Thanks.
A classpath resource is not the same as a file.
As you have correctly stated, the full path of a file is something like D:\desktop\level3.txt.
But if ever want to distribute your application so it can run on other computers, which probably won’t have that file in that location, you have two choices:
Ask the user to tell the program where to find the file on their computer.
Bundle the file with the compiled program.
If you place a non-.class file in the same place as .class files, it’s considered a resource. Since you don’t know at runtime where your program’s class files are located,¹ you use the getResource or getResourceAsStream method, which is specifically designed to look in the classpath.
The getResource* methods have the additional benefit that they will work both when you are developing, and when the program is packaged as a .jar file. Individual entries in a .jar file are not separate files and cannot be read using the File or FileInputStream classes.
If I understand your assignment correctly, the default level file should be an application resource, and the name of that resource is what should be hard-coded in your program. Something like:
InputStream is;
if (args.length > 0) {
is = new BufferedInputStream(
new FileInputStream(args[0]));
} else {
// No argument provided, so use program's default level data.
is = ClassLoader.getSystemClassLoader().getResourceAsStream("defaultlevel.txt");
}
chosenLevels = level.makeLevel(is);
¹ You may find some pages that claim you can determine the location of a running program’s code using getProtectionDomain().getCodeSource(), but getCodeSource() may return null, depending on the JVM and ClassLoader implementation, so this is not reliable.
To answer your first question, it doesn't seem like they're asking you to read from anywhere on disk, just from within your class path. So that seems fine.
The second question, "What does is mean 'should be hard-coded in your code, and be relative to the classpath'?". You are always going to have a default level file in your project directory. Define the path to this file as a String in your program and that requirement will be satisfied. It's related to the InputStream because the stream requires a location to read in from.
I'm trying to make a little program from school better, because I am more advanced then the others in my class and want to have a bit fun. It is a simple command line program in java but I want to make it with a full GUI.
So basically I want to access the JAR-File when executed and print the code written in a (by menu selected) class-file. I already know how to find the JAR-File and this works, but I can't find any way to get INTO the JAR-File. I tried creating a File object and putting the path to the JAR combined with the path to the class file I want to access. (Ex: "C:\temp\Test\program.jar\de\bbzsogr\Main.class" as found in WinRAR)
Here is some Code of the "CodeGrabber" class i wrote to access the JAR and then the file in the JAR.
public class CodeGrabber {
private static File JAR;
public static void grabCode(String className) {
try {
JAR = new File(Main.class.getProtectionDomain().getCodeSource().getLocation().toURI().getPath());
} catch (URISyntaxException e) {
e.printStackTrace();
}
System.out.println("JAR is located in: " + JAR);
// -> "JAR is located in: C:\temp\Test.jar"
System.out.println("Searching for \"" + JAR + File.separator + "ch" + File.separator + "bbzsogr" + File.separator + "Main.class");
// -> "Searching for "C:\temp\Test.jar\ch\bbzsogr\Main.class" "
File main = new File(JAR + File.separator + "ch" + File.separator + "bbzsogr" + File.separator + "Main.class");
try {
Scanner scanner = new Scanner(main);
while(scanner.hasNext()) {
System.out.println(scanner.nextLine());
}
} catch (FileNotFoundException e) {
System.out.println("File MAIN not found...");
return;
}
// -> "File MAIN not found..."
}
}
I excepted to get a scrambled mess of data because the file, if I could access it, is still encoded/compiled, but I get the Message, that the wanted file was not found.
Thanks in advance!!
If you want to add and access a jar file inside a java program,you must import the java classes this jar contains and use their methods.You should write something like
import prog.mainclass
at the beginning of your program rather than trying to access it through the Jar.
For what you are asking now,the reason your program can't find the jar is because the path you imported is not valid.Java can't search inside any program but only inside a filesystem.Any path should be without dots like C:/temp/path and can't be,for example C:/temp.csv/path
TLDR: jar entries are not files.
A jar file is a file -- note 'a' meaning 'one'. A jar file is typically created by taking several files (often as many as hundreds, thousands, or more), usually at least some of them java (compiled) class files, (usually) compressing the data from each one, and writing the (compressed) data and name for each file as an entry in the jar. It is possible however for a jar entry not to come from a file; for example the manifest entry is often created 'on the fiy' rather than read from a file, and for a signed jar the signature entries always are. But even for jar entries that are created from files, the jar entries themselves are not files, and cannot be accessed as files using basic pre-NIO file access.
You have three options.
For a jar in the classpath -- which your jar obviously is, since you found it as the source for a loaded class, ClassLoader allows you to read any entry as a 'resource'. This is normally used for things like images, audio, video or other data packed in a jar with an application, but it works on entries that are class files.
// you can invoke it based on a known class like
InputStream is = Main.class.getResourceAsStream("path/for/package/Foo.class");
// or globally
InputStream is = ClassLoader.getResourceAsStream("path/for/package/Foo.class");
jar files are really zip files 'underneath', and Java has long allowed you to access zip files using java.io.ZipFile (directly) or java.io.ZipInputStream (layered on a FileInputStream). The former allows you to access entries 'randomly' using the so-called central directory, while the latter requires you to access entries in the order they occur in the file (and works on nonseekable underlying file forms like pipes and socket connections, but you don't need that here) which makes it a little less convenient for your purpose but still workable. See the javadoc for either.
NIO in Java 7 up (and pretty much everybody should be there by now) adds support for alternate filesystems which provide file-like (or at least stream-like) access to things other than actual files supported by the underlying operating system or its file system(s). And although more can be added, it comes with one alternate provider already installed which handles jars (really zips) -- just as you want.
String jarname = Main.class.getProtectionDomain().getCodeSource().getLocation().getPath();
FileSystem fs = FileSystems.newFileSystem(Paths.get(jarname), null);
InputStream is = Files.newInputStream(fs.getPath("package/Foo.class"));
Note that in all cases I've opened an InputStream, not a Reader (or Scanner). Reader and Scanner are for text consisting of characters, and in most cases lines (which by definition contain characters). Class files have some characters here and there, but are mostly not characters and thus not text; they need to be read and processed as binary (with the few parts that are characters converted if desired). Have fun.
I try to write and read to the file in my java project file called Books.txt.
The problem is that I can access the file only if partialPath has full path to the file.
Here is the code:
public <T> List<T> readFromFile(String fileName) {
private String partialPath = "\\HW3\\src\\java\\repos\\";
try {
String path = partialPath + fileName;
FileInputStream fi = new FileInputStream(path);
ObjectInputStream oi = new ObjectInputStream(fi);
// Read objects
List<T> items = (List<T>) oi.readObject();
oi.close();
fi.close();
return items;
} catch (IOException | ClassNotFoundException e) {
}
}
If I set relative path as above I get exception file not found.
My question is how can I set full path to the current directory programmatically?
Here is a code snippet of the Drombler Commons - Client Startup code I wrote, to determine the location of the executable jar. Replace DromblerClientStarter with your main class.
This should work at least when you're running your application as an executable JAR file.
/**
* The jar URI prefix "jar:"
*/
private static final String FULL_JAR_URI_PREFIX = "jar:";
/**
* Length of the jar URI prefix "jar:"
*/
private static final int FULL_JAR_URI_PREFIX_LENGTH = 4;
private Path determineMainJarPath() throws URISyntaxException {
Class<DromblerClientStarter> type = DromblerClientStarter.class;
String jarResourceURIString = type.getResource("/" + type.getName().replace(".", "/") + ".class").toURI().
toString();
int endOfJarPathIndex = jarResourceURIString.indexOf("!/");
String mainJarURIString = endOfJarPathIndex >= 0 ? jarResourceURIString.substring(0, endOfJarPathIndex)
: jarResourceURIString;
if (mainJarURIString.startsWith(FULL_JAR_URI_PREFIX)) {
mainJarURIString = mainJarURIString.substring(FULL_JAR_URI_PREFIX_LENGTH);
}
Path mainJarPath = Paths.get(URI.create(mainJarURIString));
return mainJarPath;
}
Depending on where you bundle Books.txt in your application distribution package, you can use this mainJarPath to determine the path of Books.txt.
I also feel that files created (and later possibly modified and or deleted) by your running Java application is usually better to be placed in a location of the file system that is away from your java application installed home directory. An example might be the 'C:\ProgramData\ApplicationNameFiles\' for the Windows operating system or something similar for other OS platforms. In my opinion, at least for me, I feel it provides less chance of corruption to essential application files due to a poorly maintained drive or, accidental deletion by a User that opens up a File Explorer and decides to take it upon him/her self to clean their system of so called unnecessary files, and other not so obvious reasons.
Because Java can run on almost any platform and such data file locations are platform specific the User should be allowed to select the location to where these files can be created and manipulated from. This location then can be saved as a Property. Indeed, slightly more work but IMHO I feel it may be well worth it.
It is obviously much easier to create a directory (folder) within the install home directory of your JAR file when it's first started and then store and manipulate your application's created data files from there. Definitely much easier to find but then again...that would be a matter of opinion and it wouldn't be mine. Never-the-less if you're bent on doing it this way then your Java application's Install Utility should definitely know where that install path would be, it is therefore just a matter of storing that location somewhere.
No Install Utility? Well then your Java application will definitely need a means to know from where your JAR file is running from and the following code is one way to do that:
public String applicationPath(Class mainStartupClassName) {
try {
String path = mainStartupClassName.getProtectionDomain().getCodeSource().getLocation().getPath();
String pathDecoded = URLDecoder.decode(path, "UTF-8");
pathDecoded = pathDecoded.trim().replace("/", File.separator);
if (pathDecoded.startsWith(File.separator)) {
pathDecoded = pathDecoded.substring(1);
}
return pathDecoded;
}
catch (UnsupportedEncodingException ex) {
Logger.getLogger("applicationPath() Method").log(Level.SEVERE, null, ex);
}
return null;
}
And here is how you would use this method:
String appPath = applicationPath(MyMainStartupClassName.class);
Do keep in mind that if this method is run from within your IDE it will most likely not return the path to your JAR file but instead point to a folder where your classes are stored for the application build.
This is not a unique issue to Java, it's a problem faced by any developer of any language wishing to write data locally to the disk. The are many parts to this problem.
If you want to be able to write to the file (and presumably, read the changes), then you need to devise a solution which allows you find the file in a platform independent way.
Some of the issues
The installation location of the program
While most OS's do have some conventions governing this, this doesn't mean they are always used, for what ever reason.
Also, on some OS's, you are actively restricted from writing to the "installation" location. Windows 8+ doesn't allow you to write to the "Program Files" directory, and in Java, this usually (or at least when I was dealing with it) fails silently.
On MacOS, if you're using a "app bundle", the working directory is automatically set to the user's home directory, making it even more difficult to manage
The execution context (or working directory) may be different from the installation location of the program
A program can be installed in one location, but executed from a different location, this will change the working directory location. Many command line tools suffer from this issue and use different conventions to work around it (ever wonder what the JAVA_HOME environment variable is for 🤔)
Restricted disk access
Many OS's are now actively locking down the locations to which programs can write, even with admin privileges.
A reusable solution...
Most OS's have come up with conventions for solving this issue, not just for Java, but for all developers wishing to work on the platform.
Important Like all guide lines, these are not hard and fast rules, but a recommendations made by the platform authors, which are intended to make your life simpler and make the operation of the platform safer
The most common solution is to simply place the file in a "well known location" on the disk, which can be accessed through an absolute path independently of the installation or execution location of the program.
On Windows, this means placing the file in either ~\AppData\Local\{application name} or ~\AppData\Roaming\{application name}
On MacOS, this means placing the file in ~/Library/Application Data/{application name}
On *nix, this typically means placing the file in ~/.{application name}
It could be argued that you could use ~/.{application name} on all three platforms, but as a user who "shows hidden files", I'd prefer you didn't pollute my home directory.
A possible, reusable, solution...
When Windows 8 came out, I hit the "you can't write to the Program Files" issue, which took some time to diagnose, as it didn't generate an exception, it just failed.
I was also working a lot more on Mac OS as well, so I needed a simple, cross platform solution, so my code could automatically adapt without the need for multiple branches per platform.
To this end, I came with a simple utility class...
public enum SystemUtilities {
INSTANCE;
public boolean isMacOS() {
return getOSName().startsWith("Mac");
}
public boolean isMacOSX() {
return getOSName().startsWith("Mac OS X");
}
public boolean isWindowsOS() {
return getOSName().startsWith("Windows");
}
public boolean isLinux() {
return getOSName().startsWith("Linux");
}
public String getOSName() {
return System.getProperty("os.name");
}
public File getRoamingApplicationSupportPath() {
// For *inx, use '~/.{AppName}'
String path = System.getProperty("user.home");
if (isWindowsOS()) {
path += "\\AppData\\Roaming";
} else if (isMacOS()) {
path += "/Library/Application Support";
}
return new File(path);
}
public File getLocalApplicationSupportPath() {
// For *inx, use '~/.{AppName}'
String path = System.getProperty("user.home");
if (isWindowsOS()) {
path += "\\AppData\\Local";
} else if (isMacOS()) {
path += "/Library/Application Support";
}
return new File(path);
}
}
This provides a baseline from which "independent" code can be built, for example, you could use something like...
File appDataDir = new File(SystemUtilities.INSTANCE.getLocalApplicationSupportPath(), "MyAwesomeApp");
if (appDataDir.exists() || appDataDir.mkdirs()) {
File fileToWrite = new File(appDataDir, "Books.txt");
//...
}
to read/write to the file. Although, personally, I might have manager/factory do this work and return the reference to the end File, but that's me.
What about "pre-packaged" files?
Three possible solutions...
Create the file(s) if they don't exist, populating them with default values as required
Copy "template" file(s) out of the Jar file, if they don't exist
Use an installer to install the files - this is the solution we used when we were faced with changing the location of all our "external" configuration files.
Read only files...
For read only files, the simplest solution is to embedded them within the Jar as "embedded resources", this makes it easier to locate and manage...
URL url = getClass().getResource("/path/to/readOnlyResource.txt");
How you do this, will depend on your build system
My resources folder inside my jar includes a directory with several binary files. I am attempting to use this code to extract them:
try(InputStream is = ExternalHTMLThumbnail.class.getResourceAsStream("/wkhtmltoimage")) {
Files.copy(is, Paths.get("/home/dan/wkhtmltoimage");
}
This is throwing the error
java.nio.file.NoSuchFileException: /home/dan/wkhtmltoimage
Which comes from
if (errno() == UnixConstants.ENOENT)
return new NoSuchFileException(file, other, null);
in UnixException.java. Even though in Files.java the correct options are passed:
ostream = newOutputStream(target, StandardOpenOption.CREATE_NEW,
StandardOpenOption.WRITE);
from Files.copy. Of course there's not! That's why I'm trying to make it. I don't yet understand Path and Files enough to do this right. What's the best way to extract the directory and all its contents?
Confused because the docs for Files.copy claims
By default, the copy fails if the target file already exists or is a symbolic link
(Apparently it fails if the target file doesn't exist as well?)
And lists the possible exceptions, and NoSuchFileException is not one of them.
If you're using Guava:
URL url = Resources.getResource(ExternalHTMLThumbnail.class, "wkhtmltoimage");
byte[] bytes = Resources.toByteArray(url);
Files.write(bytes, new File("/my/path/myFile"));
You could of course just chain that all into one line; I declared the variables to make it more readable.
The file that does not exist may actually be the directory you're trying to create the file in.
/home/dan/wkhtmltoimage
Does /home/dan exist? Probably not if you're on a Mac.