I am trying to load a Java file to my android project using URLClassLoader class.
But I got a ClassNotFoundError when I run the below code:
package com.xyz.abc;
public class ConstantClassReader
{
public void readFile() throws MalformedURLException
{
String url = "file://"+Environment.getExternalStorageDirectory().getPath()+"/StringConstants.class";
URLClassLoader urlClassLoader = URLClassLoader.newInstance(new URL[]{new URL(url)});
Class simpleClass = null;
try {
simpleClass = urlClassLoader.loadClass("com.xyz.abc.StringConstants");
Constructor simpleConstructor = simpleClass.getConstructor();
Object simpleClassObj = simpleConstructor.newInstance();
Method method = simpleClass.getMethod("myMethod");
method.invoke(simpleClassObj);
} catch (ClassNotFoundException | NoSuchMethodException | IllegalAccessException | InstantiationException | InvocationTargetException e) {
e.printStackTrace();
}
}
}
The below Java file is resides inside my SDCard:
package com.xyz.abc;
public class StringConstants{
public void myMethod(){
System.out.println("myMethod Loaded");
}
}
A class loader loads classes.
Putting a Java source file somewhere will not do.
You have to compile that file; and maybe you are lucky then. But I would be rather surprised if the Android JVM allows you to load classes from arbitrary places. This screams: "security problem" all over the place.
Given your requirement, I would suggest a different solution, something on top of ordinary Java properties. Meaning: it seems that you simply want to provide some "configuration" information dynamically to your Java app. Then have that app read a properties file that contains key/value pairs. That is (almost) business as usual; and not leading to a need to load class files in arbitrary places.
http://www.beanshell.org/
you can embed beanshell, load the java file, instance and execute whatever.
access the java file as any other plain file from the sd
How can I read a text file from the SD card in Android?
Related
I am working on refactoring a huge collection of code in an enterprise-scaled application.
The first step I want to take is to localize the log messages, so I decided to use the StringManager that I saw when I was reading the source code of Apache Tomcat.
The basic idea is to declare a file named LocalString.properties in the packages and define log messages in them. So in a way, this localizes the Strings to scope of that package only, so that it looks like:
Application
`----src
| `----main
| `----java
| `----package
| `----BusinessAction.java
| `----LocalStrings.properties
`----pom.xml
And an object of this StringManager is instantiated as:
private static final StringManager sm = StringManager.getManager("package");
This is constructor of the StringManager class.
/**
* Creates a new StringManager for a given package. This is a
* private method and all access to it is arbitrated by the
* static getManager method call so that only one StringManager
* per package will be created.
*
* #param packageName Name of package to create StringManager for.
*/
private StringManager(String packageName) {
String bundleName = packageName + ".LocalStrings";
try {
bundle = ResourceBundle.getBundle(bundleName, Locale.getDefault());
} catch( MissingResourceException ex ) {
ClassLoader cl = Thread.currentThread().getContextClassLoader();
if( cl != null ) {
try {
bundle = ResourceBundle.getBundle(
bundleName, Locale.getDefault(), cl);
} catch(MissingResourceException ex2) {
// Ignore
}
}
}
// Get the actual locale, which may be different from the requested one
if (bundle != null) {
locale = bundle.getLocale();
}
}
This works absolutely fine when used in a standalone application, but is causing issues when used in a web application.
I don't understand why the bundle fails to load on the first hand, but in the catch block, I can definitely tell that it is because the classloader is an instance of WebappClassloader.
What mistake I am making here? Also, what would be the good ways to load this property file?
Edit
The problem here is with the Maven build. Maven process all resources in src/main/resources and ignores the ones in src/main/java. So in the end, the compiled application did't had the LocalStrins.properties file where it was supposed to be.
Solved it by creating package in src/main/resources and putting the file in there.
The reason it fails is simply that the LocalStrings.properties file is not in your war file. In a Maven project, resources are supposed to be under src/main/resources. Not src/main/java. Maven will simply ignore resources under src/main/java, so they won't be in the built webapp at all, and thus won't be available at runtime.
I am trying to develop a module that can update my running Java Desktop App.
The problem is that I have to replace the actual running jar with another jar, all the while displaying an image and a progress bar with the remaining time of the update process.
One solution I thought about is that I can put a jar in my main jar, and when launching the update process, to extract that second jar which will display the image and the progess bar, and also which will replace the old main jar with a new main jar.
My question is if this is possible and how can I do it.
I do not have a lot of experience with java and java packaging so if you have any examples or links, it would be of great help for me.
Thank you very much.
R.
Run this code when press UPDATE button ..
if(Desktop.isDesktopSupported()){
try {
Desktop.getDesktop().open(new File("update.jar"));
System.exit(0);
} catch (IOException ex) {
}
}
This will open update.jar and close main.jar. Now run this code from main class of update.jar
//wait sometime for terminate main.jar
try{
Thread.sleep(5000);
} catch(Exception e){
e.printStackTrace();
}
if(isUpdateVersionAvailable()) { //first check update from database
if(copyMainJarFileFromServer()){ //copy newMain.jar from server and paste
new File("main.jar").delete(); //delete main.jar
rename(new File("newMain.jar")); //rename newMain.jar to main.jar
}
}
boolean isUpdateVersionAvailable() {
//todo
}
boolean copyMainJarFileFromServer() {
//todo
}
void rename(File file){
file.renameTo(new File("main.jar"));
}
You can have a starter jar that checks for updates and launches the app from the main jar.
It will show start logo, an image, that standard java can display at start-up.
The start0er could also be used to restart the app in another interface language.
package starter;
...
public class StarterApp {
public static void main(String[] args) {
String workDir = System.getProperty("user.dir");
Path mainJar = Paths.get(workDir + "...");
Path nextMainJar = Paths.get(workDir + "...");
if (Files.exists(nextMainJar)) {
Files.copy(nextMainJar, mainJar, StandardCopyAction.REPLACE_EXISTING);
}
URLClassLoader classLoader = new URLClassLoader(new URL[] {mainJar.toURL()});
Class<?> appClass = classLoader.find("mainjar.MainApp");
... instantiate the app
}
As you see the main jar must not be loaded from too early, maybe not be on the class path entirely, and hence the use of a separate ClassLoader. The same might probably be done with the main jar on the class path of the starter app, and using Class.forName("mainjar.MainApp"). The Class-Path can be specified in META-INF/MANIFEST.MF.
The secundary jars may reside in a lib/ directory.
For those readers wanting more modular, service oriented, updateable apps, one could make an OSGi application, a container for bundles (=jars), that provide exchangable services and life-time control.
My current java project is using methods and variables from another project (same package). Right now the other project's jar has to be in the classpath to work correctly. My problem here is that the name of the jar can and will change because of increasing versions, and because you cannot use wildcards in the manifest classpath, it's impossible to add it to the classpath. So currently the only option of starting my application is using the -cp argument from the command line, manually adding the other jar my project depends on.
To improve this, I wanted to load the jar dynamically and read about using the ClassLoader. I read a lot of examples for it, however I still don't understand how to use it in my case.
What I want is it to load a jar file, lets say, myDependency-2.4.1-SNAPSHOT.jar, but it should be able to just search for a jar file starting with myDependency- because as I already said the version number can change at anytime. Then I should just be able to use it's methods and variables in my Code just like I do now (like ClassInMyDependency.exampleMethod()).
Can anyone help me with this, as I've been searching the web for a few hours now and still don't get how to use the ClassLoader to do what I just explained.
Many thanks in advance
(Applies to Java version 8 and earlier).
Indeed this is occasionally necessary. This is how I do this in production. It uses reflection to circumvent the encapsulation of addURL in the system class loader.
/*
* Adds the supplied Java Archive library to java.class.path. This is benign
* if the library is already loaded.
*/
public static synchronized void loadLibrary(java.io.File jar) throws MyException
{
try {
/*We are using reflection here to circumvent encapsulation; addURL is not public*/
java.net.URLClassLoader loader = (java.net.URLClassLoader)ClassLoader.getSystemClassLoader();
java.net.URL url = jar.toURI().toURL();
/*Disallow if already loaded*/
for (java.net.URL it : java.util.Arrays.asList(loader.getURLs())){
if (it.equals(url)){
return;
}
}
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(loader, new Object[]{url});
} catch (final java.lang.NoSuchMethodException |
java.lang.IllegalAccessException |
java.net.MalformedURLException |
java.lang.reflect.InvocationTargetException e){
throw new MyException(e);
}
}
I needed to load a jar file at runtime for both java 8 and java 9+. Here is the method to do it (using Spring Boot 1.5.2 if it may relate).
public static synchronized void loadLibrary(java.io.File jar) {
try {
java.net.URL url = jar.toURI().toURL();
java.lang.reflect.Method method = java.net.URLClassLoader.class.getDeclaredMethod("addURL", new Class[]{java.net.URL.class});
method.setAccessible(true); /*promote the method to public access*/
method.invoke(Thread.currentThread().getContextClassLoader(), new Object[]{url});
} catch (Exception ex) {
throw new RuntimeException("Cannot load library from jar file '" + jar.getAbsolutePath() + "'. Reason: " + ex.getMessage());
}
}
I've a javafx application which is run by web start. In my fx application, I try to load the classes using ClassLoader as in below code. The parameter passed is a package name like "com.example.project.abcd"
public final static List<Class<?>> find(final String scannedPackage)
{
final ClassLoader classLoader = Thread.currentThread().getContextClassLoader();
final String scannedPath = scannedPackage.replace(DOT, SLASH);
final Enumeration<URL> resources;
try {
resources = classLoader.getResources(scannedPath);
} catch (IOException e) {
throw new IllegalArgumentException(String.format(BAD_PACKAGE_ERROR, scannedPath, scannedPackage), e);
}
final List<Class<?>> classes = new LinkedList<Class<?>>();
while (resources.hasMoreElements()) {
final File file = new File(resources.nextElement().getFile());
classes.addAll(find(file, scannedPackage));
}
return classes;
}
Now I'm not able to get all the classes present inside "com.example.project.abcd" package when I run it thru java web start but through IDE it is working fine.
I'm using JDK 7, JavaFX 2.
As per http://docs.oracle.com/javase/7/docs/technotes/guides/javaws/developersguide/faq.html#s211 Thread.currentThread().getContextClassLoader() should work fine but it is not!.
Tried searching on net/googling but in vain. Checked http://lopica.sourceforge.net/faq.html#customcl as well and tried using URLClassLoader as suggested. But that didn't work as well (Though did not know what should be passed to the parameter 'urls')
Any help is much apreciated.
I think this works in IDE because your BIN/classes directory is used to get all the files.
In Webstart-Mode, all your classes are inside JARs.
EDITED with solution (below...)
I have a Splash screen that is packaged into it's own jar. It works.
I can call the Splash.jar from inside another java app by:
Desktop.getDesktop().open(new File("/Applications/Eclipse/Splash.jar"));
and it works just fine. But, that's pretty limited. So, I created a res folder in the new app and dragged the Splash.jar into it.
Now, how do I call it/run it from the main class of the new app??
I looked at all the related posts and see no clear approach...
Thanks
SOLUTION:
I found the solution - so simple. First, the credit to avjava.com for their clear and excellent tutorial on doing this ( Run Another Jar from a Jar ). So, now I can run the Splash (or other .jar) just as hoped for.
The answer is here - posted in the form of complete code:
package test;
import java.io.IOException;
public class RuntimeExecTest1 {
public static void main(String[] args) {
try {
System.out.println("TextEdit I hope");
Runtime runTime = Runtime.getRuntime();
Process process = runTime.exec(
"java -jar /your directory/your app.jar");
try {
Thread.sleep(5000); // keep in open 5000ms
} catch (InterruptedException e) {
e.printStackTrace();
}
System.out.println("Closing TextEdit, I hope");
process.destroy(); // kill the process of running the .jar
} catch (IOException e) {
e.printStackTrace();
}
}
}
We don't know how your existing Splash Screen works...
Java AWT support for Splash Screen:
If you are using the Java built-in support for splash screens (as specified in SplashScreen Javadoc) then you need to use a command line switch, or better yet, modify your MANIFEST file in order to reference your Splash Screen:
Manifest-Version: 1.0
Main-Class: Test
SplashScreen-Image: filename.gif
I don't know if, for this particular case, you can reference files in a different JAR. In the worst case, you can unpack the existing JAR (they are just ZIP files) and get the image file in order to include it in your own main jar.
Possibly custom Splash:
If your Splash is created using custom code, then you need the documentation about how to load it. At least, you'd need to add Splash.jar to the classpath of your application and, from your app, call the necessary method or load the appropriate resource.
All the resources and classes contained in .jar files that are added to the classpath are available from your app.
You could create a new URLClassLoader with the Splash.jar and then use reflections to execute the main method.
URL[] urls = new URL[] { new URL("res/Splash.jar") };
ClassLoader cl = new URLClassLoader(urls);
Class<?> clazz = cl.loadClass("splash.Main");
Method method = clazz.getMethod("main", String[].class);
method.invoke(null, new String[0]);
add the resource path to your CLASSPATH envoirment variable and you can use it without modifying your existing code
if your running linux
export CLASSPATH=yourpath;
and if your running windows:
from My Computer right click > properties
OR
if you dont want to add it to CLASSPATH ENV variable,
then
java -classpath="your path to jar" yourclass
Why not define Splash.jar as an external jar and go about using all its routines. Much like JDBC.