Java get project file system location from another project - java

I'm working on a multi project build where i need to get all .class files within a project and then load of all those through reflection by its file system location, given this project structure:
multi-project
|_ /app/
| |_ src/
|_ /notification/
| |_ /src/
|_ settings.gradle
In this multi project build my app project is dependent on notification project like this
dependencies {
implementation project(":notification")
// other dependencies
}
Although it may look like a gradle related question at this point, it's not, it's build tool agnostic.
Currently I'm working on app project, but i need to dynamically load all the classes located on notification project, i usually do this by inspecting the file location of a project, but i need to get the location of notification project from a class that's located in app project
I load all the classes of app project by using ClassLoader.getResources() in order to capture the path, but this class loader can not capture the path of the notification project

You can't. The classpath abstraction fundamentally doesn't have the "list resources" concept. It's just "get me resource X", that's all it has.
Hence, "I want to get a list of all classes so I can load them via reflection" is not possible without hackery.
Fortunately, there is an actual solution. resource-listing. Generally, by using the SPI (Service Provider Interface) system.
During compilation (during which, the compiler/build tool certainly does have a complete list of everything it is compiling), a text file is created. It contains the fully qualified class name of each and every relevant class, one such name per line. This text file is then included in the jar files. Now you can build up the process of 'give me an instance of each class of project X' just fine using just the API exposed by java's ClassLoader:
Load resource "/meta-inf/services/com.foo.YourInterface" and read it line by line.
Per line, run Class.loadClass(thatLine).getConstructor().newInstance(), cast it to com.foo.YourInterface and add it to a list.
return the list.
Voila. No 'list' directive (which doesn't exist) required. This is exactly how java itself works when finding e.g. SQL drivers, charset encoding drivers, implementations of crypto, etcetera.
The basics for how to do this depends on your buildsystem. The compilation step ("create a text file with all implementations") is build-dependent; you could write it by hand, or you can write your own build plugin, or you can use existing annotation processors that scan for an annotation and generate it automatically.
The runtime component (using the text file that is on the classpath, i.e. in the jar where the class files also are, load each and every class listed there, instantiate them, and return it to me as a list of instances) is baked into java itself: ServiceLoader.

I think i was not very clear as it was my first question, I just needed the file system path to the other project, I could find what i was looking for by using
System.getProperty("java.class.path")
It contains the path to the compiled jar of the project i need, it's not the path i needed, but i can still get the entries of that file which is as much as useful as the path itself

Related

In Java, should I be creating a new Package, Folder, or Source Folder?

There are a couple of questions on SO that sort of hit this, but I am totally new to Java development and I don't know the correct way to approach this.
I have a C# solution, containing two projects (my app, and a unit test project) and within the app, most things are put into folders eg. Interfaces, Exceptions etc.
I am trying to recreate this in Java / Eclipse, but I don't know how. I ended up with lots of packages, which sounds really bad. I also tried adding a source folder but that ended up being outside of the package.
Could anyone point me in the right direction?
Namely, which of those should I use to represent my unit test project/set of unit tests, and subfolders which exist just for organising stuff.
Edit: It also says use of the default package is not advised. What should I be doing?
Edit 2: Here is what it looks like. Does this look vaguely correct? My original C# solution is on the right.
In a typical java eclipse project, you will have one or more source folders (for example one for app code, one for your unit tests).
Each folder contains a package tree, typically starting with your base package, for example com.mycompany.myapp.
In order to avoid name collisions, packages names are usually start with the domain name of the entity who is the author of the code, starting with the top-level-domain and going backwards (more general to more specific). That way, each class fully qualified name is unique. For example if google creates a class named List, it will be known as com.google.List, and it will not enter in conflict with the existing java.util.List interface.
You can have a unlimited number of packages inside this base package, for example :
com.mycompany.myapp.persistence
com.mycompany.myapp.domain
com.mycompany.myapp.services
com.mycompany.myapp.web
It all depends on your project and the way you want to organize your code and your classes.
At the logical level, packages are named with dots as separator. They contain java classes.
At the physical on disk level, each package is a directory. The java classes are contained in .java files (most frequently one class per file).
In Eclipse a "source folder" is a folder inside your project that is known to Eclipse to contain java source files. It will be compiled included in the output (for example JAR file) when you build your project.
In Eclipse, you usually view them at the logical level, showing packages. When you tell Eclipse to "create a new package", it will create the directory for you. For example, if you tell it to create the com.mycompany.myproject package, it will automatically create a com folder containing a mycompany folder containing a myproject folder.
In java source tree structure must match package structure
so foo.bar package must be laid out in
src/foo/bar
Also default package may not be advised - but you can still use it - better to put things in a package though
In java different project development structure are flowed according to type of project.
So as you are new to java and Eclipse so it's better to install maven plugin and create maven project and choose a archetypes according to your project type like a standalone or web based.
The maven plugin will create the project structure including packages,test packages source folder etc. You can get more about project structure from this
Using the default package may create namespace collisions. Imagine you're creating a library which contains a MyClass class. Someone uses your library in his project and also has a MyClass class in his default package. What should the compiler do? Package in Java is actually a namespace which fully identifies your project. So it's important to not use the default package in the real world projects.

Creating a Jar File Containing Another Jar File

I've created an Android project that does some very simple network stuff. This project is standalone, and I've successfully exported it as a jar file and imported it into other projects I've worked on. When exporting this project, I am ONLY exporting the class files that are used for the project. (No system files: ie. Manifest, etc)
I want to leave this project as standalone since it is used in other projects and will be used more in the future.
However, we have other projects that need the features of the network jar and some other features. What I would like to do is create a new project, import the network jar file. Create wrappers for all the functions in the network jar file, then add more functionality.
Afterwards I want to package this project up as a jar file, so that I can use it solely for projects that require that extra functionality.
Having this project as a jar is important, because this component might be given to other people to use, and easy/simple integration is required.
This seamed like it should be pretty straight forward to me. But I am getting noClassDef errors and Verify errors.
I even stripped the project down to a single function that returns a static String from the original network jar and passed that through and still got a "could not find method xxxx referenced from method xxxx" log and then a unable to resolve static method warning, then a No ClassDefFoundError. Here's the logcat.
What is the proper way to do this?
Thanks!
Jar files that want to use embedded jars must define a custom class loader, like JarClassLoader.
If you want to use a single jar file you also have options like OneJar, JarJar, and ProGuard.

Detect current app version

When I was working with XCode and iOS, there was a simple way to check the application's current version by reading the plist.
Is there a similar way to do this in Java?
XCode stores that version value in a resource file that is distributed with your application. In Java the equivalent would be your Manifest file, which is packed inside your JAR/WAR/EAR archive.
A Manifest file is just a metadata text file named MANIFEST.MF that stores some standard key/value pairs which are recognized by many tools and that is packaged inside a special folder named META-INF inside your java archive.
To get the Manifest file for your own JAR this question would give you some clues. Once you have your own Manifest instance then use either one of the next options to get that version value.
This way to get the Specification Version:
Manifest mf = .... // get the manifest file
String specVersion = mf.getAttribute("Specification-Version");
This way to get the Implementation Version:
Manifest mf = .... // get the manifest file
String specVersion = mf.getAttribute("Implementation-Version");
More info regarding the JAR manifests can be found here.
EDIT:
If you are getting null values for any of those properties that means that they haven't been configured in your MANIGEST.MF file. That's easy to check: unzip your JAR file (JAR files are just ZIP files with a different extension name) and go the META-INF folder to find the MANIFEST.MF file, since it's a text file you can print its contents to the console, if there is a Specification-Version or Implementation-Version attribute defined there and you are still getting null values then you might be loading a manifest file from a different JAR.
FOR THE RECORD:
To get that attributes in your Manifest file you would need to configure your build tool to do so. Maven would do it automatically (you can customize it though), with Ant you will need to use a specific Ant Task, with Eclipse you will need go through its docs (same with any other IDE).
As Alonso says, in Java, your code isn't automatically assigned a build version by the compiler. Java leaves that up to the build tool that your compiler is run by, e.g. ant or maven. If your app isn't using the manifest file, which is often the case, but using instead a version number suffix, e.g. my_app_1.2.3.jar then you could do this to get the jar name:
Class.getProtectionDomain().getCodeSource().getLocation()
If it has a GUI and the main purpose1 is 'update' use Java Web Start to deploy it.
For displaying the version to the user I would store the version number in the manifest of each Jar of the app., and show the Implementation-Versions in a JTable at run-time.
As an aside, to get better answers, put aside what you are trying to 'do' and instead name the 'feature' you are trying to achieve. The latter can be expressed as the application feature you would like to offer the user. It might be something like 'Can be expanded with plug-ins', 'Has free auto-upgrade for 24 months', 'Whiter, brighter, more suds'..

Problems including jar files outside the jar file containing the manifest

Basically let me first explain what I am aiming to do. I have a dynamic ETL transformer app written in JAVA. Due to the dynamic nature of this app I have to be able to add plugins jars to the app in a location outside of the apps jar file.
Basically would like to have the following directory structure:
AppFolder
|- plugins/
|- configs/
|- mainApp.jar
If possible I would like to be able to use wildcards in my manifest to dynamically add jars located in the plugins folder.
Unfortunately all I have tried so far has failed. I have tried to use both relative paths and absolute paths neither have worked (with or without wildcard).
If I however include the plugins folder in the main app's jar file itself it works fine given that I don't use wildcards.
So my question is, is it actually possible to have dependencies outside of a jar or do they always have to be contained within.
The other question is regarding the usage of wildcards. i have looked at [the java documentation] (http://java.sun.com/javase/6/docs/technotes/tools/windows/classpath.html) to no prevail unfortunately.
some examples of what I have tried so far:
../plugins/*
../plugins/plugin.jar
/abolute/path/to/plugins/*
/abolute/path/to/plugins/plugin.jar
and unfortunately none of them have done the trick so any help would be very much appreciated...
Yes you can have dependencies outside the jar. But wildcards are not supported for specify dependant jars.
The jars need to be explicitly specified in your manifest, and the location needs to be relative to where the application is run from
A better option for you may be to use the Extension Mechanism
java -Djava.ext.dirs=/abolute/path/to/plugins/ ......
If you have control of the code you could always add a JarClassLoader and load the jars dynamically.
http://java.sun.com/docs/books/tutorial/deployment/jar/jarclassloader.html

Managing Data Dependecies of Java Classes that Load Data from the Classpath at Runtime

What is the simplest way to manage dependencies of Java classes to data files present in the classpath?
More specifically:
How should data dependencies be annotated? Perhaps using Java annotations (e.g., #Data)? Or rather some build entries in a build script or a properties file? Is there build tool that integrates and evaluates such information (Ant, Scons, ...)? Do you have examples?
Consider the following scenario:
A few lines of Ant create a Jar from my sources that includes everything found on the classpath. Then jarjar is used to remove all .class files that are not necessary to execute, say, class Foo. The problem is that all the data files that class Bar depends upon are still there in the Jar. The ideal deployment script, however, would recognize that the data files on which only class Bar depends can be removed while data files on which class Foo depends must be retained.
Any hints?
This is one of the many problems Maven has already solved with it's build, dependency, and resource management. Any maven project follows a standard directory layout which dictates where you should put your Data files: in the 'resources' directories. The conventional Maven directory structure is as follows...
/
/src/
/src/main/java/
/src/main/java/App.java
/src/main/resources/
/src/main/resources/my.prod.data.or.cfg.or.whatever
/src/test/java/
/src/test/java/AppTest.java
/src/test/resources/
/src/test/resources/my.test.data.or.cfg.or.whatever
/pom.xml
The benefit of this is that all files which are contained in the 'main' (prod) resources directories are available to your application at run-time from the Classpath. All of the 'test/resources' files are available to your code during build & unit test time but are NOT included in your final artifact.
I don't think a generic solution exists for the system you describe, however, I just had a stab at reading annotations on classes using ASM, since that is used by jarjar as well. It is not hard to read the annotation data that way (pass in a ClassVisitor to the accept() method on ClassReader, and do something useful on the visitAnnotation callback). This means you can either try and include your intended behavior to jarjar or you could add it as a custom step to your build process.
Can't you refactor your project so that you have submodules that each contain the relevant files for the project itself ; Bar class and Bar related files will be packaged in their bundle while Foo ones will packed into another?
Another possibility would be to use some package naming convention to be able to filter the files you want to see i your bundles.

Categories