I am writing a java file in which i am parsing the given groovy file using GroovyClassLoader to find the class in it. To do this, i have to import some class (like org.junit.Test) and add package and add static import also. Since i am using old groovy version 1.6, i can not use compilation customizers as these classes not available in this version. So to import custom classes, i had to write custom groovy class loader by extending groovy class loader class like below,
...
public static class DefaultImportClassLoader extends GroovyClassLoader {
public DefaultImportClassLoader(ClassLoader cl){
super(cl);
}
public CompilationUnit createCompilationUnit(CompilerConfiguration config, CodeSource codeSource) {
CompilationUnit cu = super.createCompilationUnit(config, codeSource);
cu.addPhaseOperation(new SourceUnitOperation() {
public void call(SourceUnit source) throws CompilationFailedException {
//source.getAST().addImport("Test",ClassHelper.make("org.junit.Test")); //working
source.getAST().addImportPackage("org.junit.");
}}, Phases.CONVERSION);
return cu;
}
}
here add import package is not working. Would any one give right suggestion way of using addImportPackage().
I've tested your code and works perfectly for me. (with groovy-all-1.6.9.jar) (edit: groovy-all-1.6.0.jar works fine too)
How do you use your class DefaultImportClassLoader?
I've done:
public static void main(String[] args) throws InstantiationException, IllegalAccessException{
GroovyClassLoader loader = new DefaultImportClassLoader(new GroovyClassLoader());
Class groovyClass = loader.parseClass(DefaultImportClassLoader.class.getClassLoader().getResourceAsStream("so_22729226/test.groovy"));
GroovyObject groovyObject = (GroovyObject) groovyClass.newInstance();
groovyObject.invokeMethod("run", null);
}
With this Groovy class:
class so_22729226_Test {
def run(){
print Test.class
}
}
And I get the expected output: interface org.junit.Test
If I use the standard loader I get:
Caused by: groovy.lang.MissingPropertyException: No such property: Test for class: so_22729226_Test
Which is the expected behaviour too.
Related
I am trying to invoke a dynamically created Junit test class using the code below
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
URL classUrl = javaClass.getParent().toFile().toURI().toURL();
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { classUrl });
Class<?> clazz = Class.forName(fileName, true, classLoader);
Object obj = clazz.newInstance();
context.getLogger().log("Test Class Loader==>"+obj.getClass().getClassLoader()+"\n");
JUnitCore junit = new JUnitCore();
context.getLogger().log("JUnitCore Class Loader==>"+junit.getClass().getClassLoader()+"\n");
junit.addListener(new TextListener(new PrintStream(outputStream)));
Result result = junit.run(clazz);
return outputStream.toString();
Dynamically created test file
public class SampleJavaFileTest {
String EXPECTED_OUTPUT_STRING="r3plac3";
#Test
public void testReplaceString() {
SampleJavaFile sample = new SampleJavaFile();
String outputString = sample.replaceString("replace","e","3");
Assert.assertEquals(EXPECTED_OUTPUT_STRING, outputString);
}
}
But I get the error as
There was 1 failure:
1) initializationError(JUnitTest)
org.junit.runners.model.InvalidTestClassError: Invalid test class 'JUnitTest':
1. No runnable methods
at org.junit.runners.ParentRunner.validate(ParentRunner.java:511)
at org.junit.runners.ParentRunner.<init>(ParentRunner.java:101)
at org.junit.runners.BlockJUnit4ClassRunner.<init>(BlockJUnit4ClassRunner.java:84)
at org.junit.runners.JUnit4.<init>(JUnit4.java:23)
at org.junit.internal.builders.JUnit4Builder.runnerForClass(JUnit4Builder.java:10)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.internal.builders.AllDefaultPossibilitiesBuilder
.runnerForClass(AllDefaultPossibilitiesBuilder.java:37)
at org.junit.runner.Computer.getRunner(Computer.java:50)
at org.junit.runner.Computer$1.runnerForClass(Computer.java:31)
at org.junit.runners.model.RunnerBuilder.safeRunnerForClass(RunnerBuilder.java:70)
at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:125)
at org.junit.runners.model.RunnerBuilder.runners(RunnerBuilder.java:111)
at org.junit.runners.Suite.<init>(Suite.java:81)
at org.junit.runner.Computer$2.<init>(Computer.java:33)
I tried printing the class loaders of the dynamically created class and the JUnitCore class the results are
Test Class Loader==>java.net.FactoryURLClassLoader#86be70a
JUnitCore Class Loader==>java.net.URLClassLoader#49c2faae
Upon going through various posts the answers provided are to use custom class loaders to resolve this issue. Can you assist on how to create a custom class loader to resolve this issue?
If not custom class loaders , how else can this be resolved?
Thanks!
This might be class loader related issue, try to create URLClassLoader by using constructor and pass class loader of other test class (or just junit class) as is parent class loader, to ensure that JUnit classes are always loaded by the same class loader. And double check that this is right annotation, with right package.
public URLClassLoader(URL[] urls, ClassLoader parent)
so
new URLClassLoader(urlOfToClass, SomeTestOrJUnitClass.class.getClassLoader())
https://docs.oracle.com/javase/7/docs/api/java/net/URLClassLoader.html#URLClassLoader(java.net.URL[],%20java.lang.ClassLoader)
this exception is generated in this junit block of code
List<Method> methods = testClass.getAnnotatedMethods(Test.class);
if (methods.size() == 0) {
errors.add(new Exception("No runnable methods"));
}
So, I'd recommend to double check, that you definitely have annotation #Test for method, and it is really org.junit.Test and this annotation available in runtime.
You can check this by taking klass.getDeclaredMethod("testReplaceString") and printing all annotations from it.
If it doesn't help, then, you can debug Junit library, put breakpoint to exception (please note, this exceptions generated not in the same place, where throwed) and check conditions
BTW, what version of junit do you use?
EDITED:
I've checked your code, I've created a new project in IDEA, added junit 4.13-rc-1 to dependencies and created two files.
public class SampleJavaFileTest {
String EXPECTED_OUTPUT_STRING="r3plac3";
#Test
public void testReplaceString() {
Assert.assertEquals(EXPECTED_OUTPUT_STRING, "r3plac3");
}
}
and another class
public class ClassLoadDynamically {
public static void main(String[] args) throws Exception {
final File fileForClass = new File(SampleJavaFileTest.class.getProtectionDomain().getCodeSource().getLocation().getPath());
URLClassLoader classLoader = URLClassLoader.newInstance(new URL[] { fileForClass.toURI().toURL() });
Class<?> clazz = Class.forName("SampleJavaFileTest", true, classLoader);
Method testReplaceString = clazz.getDeclaredMethod("testReplaceString");
System.out.println("Get declared methods==>"+ testReplaceString);
System.out.println("Get annotation => "+ testReplaceString.getAnnotation(org.junit.Test.class));
JUnitCore junit = new JUnitCore();
Result result = junit.run(clazz);
System.out.println(result.wasSuccessful());
}
}
I've checked your code, it works in this way. Please check, how do you generate your code dynamically, looks like the the issue in wrong bytecode generation, double check, how do you set annotations to the method.
However, I'm not sure, about your environment configuration on lambda. Anyway, I'd recommend to make your code run locally
I am writing an agent that changes the constructor of java.net.ServerSocket using Javassist. The very simply change I do is to append a call to the default constructor that calls a static method in my class. Even though I see while debugging that the JAR that contains the class (which is the JAR that contains the agent) is available in the classpath, I get a java.lang.NoClassDefFoundError error from java.net.ServerSocket..
Has someone come across such a problem? I am not aware of a limitation in the system classloader that could stop my class my being made available to a class from java.net.
EDIT 1: The relevant code snippet I use for class transformer:
package my.package;
import java.lang.instrument.ClassFileTransformer;
...
public class QClassFileTransformer implements ClassFileTransformer {
...
public byte[] transform(...) {
...
classPool.insertClassPath(new ByteArrayClassPath(className, classfileBuffer));
classPool.importPackage("my.package");
CtClass ctClass = classPool.get(className.replaceAll("/", "."));
if (!ctClass.isFrozen()) {
for (CtConstructor constructor : ctClass.getDeclaredConstructors()) {
if (constructor.getParameterTypes().length != 0) {
continue;
}
constructor.insertAfter("my.package.QClassFileTransformer.call(this);");
break;
}
byte[] bytecode = ctClass.toBytecode();
ctClass.detach();
return bytecode;
}
...
In the transformed class (java.net.ServerSocket), the NoClassDefFoundError exception for my.package.QClassFileTransformer is thrown when the class is loaded.
The following code adds jar file to the build path, it works fine with Java 8. However, it throws exception with Java 9, the exception is related to the cast to URLClassLoader. Any ideas how this can be solved? an optimal solution will edit it to work with both Java 8 & 9.
private static int AddtoBuildPath(File f) {
try {
URI u = f.toURI();
URLClassLoader urlClassLoader = (URLClassLoader) ClassLoader.getSystemClassLoader();
Class<URLClassLoader> urlClass = URLClassLoader.class;
Method method = urlClass.getDeclaredMethod("addURL", URL.class);
method.setAccessible(true);
method.invoke(urlClassLoader, u.toURL());
} catch (NoSuchMethodException | SecurityException | IllegalArgumentException | InvocationTargetException | MalformedURLException | IllegalAccessException ex) {
return 1;
}
return 0;
}
You've run into the fact that the system class loader is no longer a URLClassLoader. As indicated by ClassLoader::getSystemClassLoader's return type, this was an implementation detail, albeit one that a non-negligible amount of code relied upon.
Judging by the comments, you are looking for a way to dynamically load classes at run time. As Alan Bateman points out, this can not be done in Java 9 by appending to the class path.
You should instead consider creating a new class loader for that. This has the added advantage that you'll be able to get rid of the new classes as they are not loaded into the application class loader. If you're compiling against Java 9, you should read up on layers - they give you a clean abstraction for loading an entirely new module graph.
I have stumbled over this issue a while ago. As many, I had used a method similar to that in the question
private static int AddtoBuildPath(File f)
to dynamically add paths to the classpath at runtime. The code in the question is probably bad style in multiple aspects: 1) assuming that ClassLoader.getSystemClassLoader() returns an URLClassLoader is an undocumented implementation detail and 2) using reflection to make addURL public is maybe another one.
Cleaner way to dynamically add classpaths
In case that you need to use the additional classpath URLs for class loading through „Class.forName“, a clean, elegant and compatible (Java 8 to 10) solution is the following:
1) Write your own class loader by extending URL classloader, having a public addURL method
public class MyClassloader extends URLClassLoader {
public MyClassloader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
2) Declare a (singleton/app wide) object of your classloader
private final MyClassloader classLoader;
and instanciate it via
classLoader = new MyClassloader(new URL[0], this.getClass().getClassLoader());
Note: The system class loader is the parent. Classes loaded though classLoader know those who can be loaded through this.getClass().getClassLoader() but not the other way around.
3) Add additional classpaths whenever needed (dynamically):
File file = new File(path);
if(file.exists()) {
URL url = file.toURI().toURL();
classLoader.addURL(url);
}
4) Instanciate objects or your app though your singleton classloader via
cls = Class.forName(name, true, classLoader);
Note: Since class loaders try a delegation to the parent class loader prior loading a class (and the parent to its parent), you have to make sure that the class to load is not visible to the parent class loader to make sure that it is loaded through the given class loader. To make this clearer: if you have ClassPathB on your system class path and later add ClassPathB and some ClassPathA to your custom classLoader, then classes under ClassPathB will be loaded through the system classloader and classes under ClassPathA are not known to them. However, if you remove ClassPathB from you system class path, such classes will be loaded through your custom classLoader, and then classes under ClassPathA are known to those under ClassPathB.
5) You may consider passing your class loader to a thread via
setContextClassLoader(classLoader)
in case that thread uses getContextClassLoader.
If you're just looking to read the current classpath, for example because you want to spin up another JVM with the same classpath as the current one, you can do the following:
object ClassloaderHelper {
def getURLs(classloader: ClassLoader) = {
// jdk9+ need to use reflection
val clazz = classloader.getClass
val field = clazz.getDeclaredField("ucp")
field.setAccessible(true)
val value = field.get(classloader)
value.asInstanceOf[URLClassPath].getURLs
}
}
val classpath =
(
// jdk8
// ClassLoader.getSystemClassLoader.asInstanceOf[URLClassLoader].getURLs ++
// getClass.getClassLoader.asInstanceOf[URLClassLoader].getURLs
// jdk9+
ClassloaderHelper.getURLs(ClassLoader.getSystemClassLoader) ++
ClassloaderHelper.getURLs(getClass.getClassLoader)
)
By default the final fields in the $AppClassLoader class cannot be accesed via reflection, an extra flag needs to be passed to the JVM:
--add-opens java.base/jdk.internal.loader=ALL-UNNAMED
I was given a spring boot application that runs in Java 8. I had the task to upgrade it to Java 11 version.
Issue faced:
Caused by: java.lang.ClassCastException: jdk.internal.loader.ClassLoaders$AppClassLoader (in module: java.base) cannot be cast to java.net.URLClassLoader (in module: java.base)
Way around used:
Create a class:
import java.net.URL;
/**
* This class has been created to make the code compatible after migration to Java 11
* From the JDK 9 release notes: "The application class loader is no longer an instance of
* java.net.URLClassLoader (an implementation detail that was never specified in previous releases).
* Code that assumes that ClassLoader.getSytemClassLoader() returns a URLClassLoader object will
* need to be updated. Note that Java SE and the JDK do not provide an API for applications or
* libraries to dynamically augment the class path at run-time."
*/
public class ClassLoaderConfig {
private final MockClassLoader classLoader;
ClassLoaderConfig() {
this.classLoader = new MockClassLoader(new URL[0], this.getClass().getClassLoader());
}
public MockClassLoader getClassLoader() {
return this.classLoader;
}
}
Create Another class:
import java.net.URL;
import java.net.URLClassLoader;
public class MockClassLoader extends URLClassLoader {
public MockClassLoader(URL[] urls, ClassLoader parent) {
super(urls, parent);
}
public void addURL(URL url) {
super.addURL(url);
}
}
Now set it in the current thread from your main class (Right at the beginning of your application)
Thread.currentThread().setContextClassLoader(new ClassLoaderConfig().getClassLoader());
Hope this solution works for your!!!
Shadov pointed to a thread at the oracle community. There is the correct answer:
Class.forName("nameofclass", true, new URLClassLoader(urlarrayofextrajarsordirs));
The caveats mentioned there are also important:
Caveats:
java.util.ServiceLoader uses the thread's ClassLoader context Thread.currentThread().setContextClassLoader(specialloader);
java.sql.DriverManager does honors the calling class' ClassLoader, -not- the Thread's ClassLoader. Create Driver directly using Class.forName("drivername", true, new URLClassLoader(urlarrayofextrajarsordirs).newInstance();
javax.activation uses the thread's ClassLoader context (important for javax.mail).
Referring to Edi's Solution this worked for me:
public final class IndependentClassLoader extends URLClassLoader {
private static final ClassLoader INSTANCE = new IndependentClassLoader();
/**
* #return instance
*/
public static ClassLoader getInstance() {
return INSTANCE;
}
private IndependentClassLoader() {
super(getAppClassLoaderUrls(), null);
}
private static URL[] getAppClassLoaderUrls() {
return getURLs(IndependentClassLoader.class.getClassLoader());
}
private static URL[] getURLs(ClassLoader classLoader) {
Class<?> clazz = classLoader.getClass();
try {
Field field = null;
field = clazz.getDeclaredField("ucp");
field.setAccessible(true);
Object urlClassPath = field.get(classLoader);
Method method = urlClassPath.getClass().getDeclaredMethod("getURLs", new Class[] {});
method.setAccessible(true);
URL[] urls = (URL[]) method.invoke(urlClassPath, new Object[] {});
return urls;
} catch (Exception e) {
throw new NestableRuntimeException(e);
}
}
}
Running within Eclipse, you need to set VM Arguments to JUnit Launch/Debug Configuration.
Running with maven via command line you have two options:
Option 1
Add following lines to pom.xml :
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-surefire-plugin</artifactId>
<version>2.16</version>
<configuration>
<argLine>--add-opens java.base/jdk.internal.loader=ALL-UNNAMED</argLine>
</configuration>
</plugin>
Option 2
run mvn test -DargLine="-Dsystem.test.property=--add-opens java.base/jdk.internal.loader=ALL-UNNAMED"
There's also this guys article that helped me.
I could not find the article but... here: https://github.com/CGJennings/jar-loader
Here's a part of guide inside there there's a jar at release you could read his guide & setup it up.
I just tried it myself download the jar file which include the class file
import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException;
import java.lang.instrument.Instrumentation;
import java.lang.reflect.Method;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.jar.JarFile;
public final class classname{
public static void premain(String agentArgs, Instrumentation instrumentation) {
loadedViaPreMain = true;
agentmain(agentArgs,instrumentation);
}
public final static void addToClassPath(File jarfile)throws IOException{inst.appendToSystemClassLoaderSearch(new JarFile(jarfile));}
public final static void agentmain(String agentArgs, Instrumentation instrumentation) {
if (instrumentation == null){throw new NullPointerException("instrumentation");}
if (inst == null) {inst = instrumentation;}
}
private static Instrumentation inst;
private static boolean loadedViaPreMain = false;
}
I just try it out myself package these code as a package then start the application class with -javaagent:plugin......jar option then call this function.It doesn't change my classpath.I am probably missing some details here.
Hope you can make it work though.
i found this, and worked for me.
String pathSeparator = Syste .getProperty("path.separator");
String[] classPathEntries = System.getProperty("java.class.path") .split(pathSeparator);
from the web site https://blog.codefx.org/java/java-11-migration-guide/#Casting-To-URL-Class-Loader
SQLUtils.java:
import org.openide.util.Lookup;
import java.util.ServiceLoader; // This doesn't work either
public class SQLUtils {
public static DBDriver getDriver(String prefix) {
for(DBDriver e : Lookup.getDefault().lookupAll(DBDriver.class)) {
System.out.println(e.getPrefix());
if(e.getPrefix().equalsIgnoreCase(prefix)) {
return e;
}
}
return null;
}
}
MySQLDriver.java:
public class MySQLDriver implements DBDriver {
#Override
public String getPrefix() {
return "mysql";
}
}
DBDriver.java:
import java.io.Serializable;
public interface DBDriver extends Serializable {
public String getPrefix();
}
Main.java:
public class Main {
public static void main(String[] args) {
DBDriver d = SQLUtils.getDriver("mysql");
}
}
This does nothing when running it, it cannot find any classes implementing.
What the program is trying to do is get the driver that is entered as a parameter for SQLUtils.getDriver(String prefix) (in Main.java).
For some reason I cannot get this to work.
I'm not familiar with OpenIDE Lookup mechanism, but I am familiar with the Java ServiceLoader mechanism.
You need to provide a file in the META-INF/services/ folder describing what classes implement specific interfaces. From the Java Docs describing the ServiceLoader class is this example:
If com.example.impl.StandardCodecs is an implementation of the
com.example.CodecSet service then its jar file also contains a file
named
META-INF/services/com.example.CodecSet
This file contains the single line:
com.example.impl.StandardCodecs # Standard codecs implementing com.example.CodecSet
What you are missing is a similar file that needs to be included on your classpath or within your JAR file.
You don't include you package names so I cannot provide a more direct example to help solve your problem.
I dropped the NetBeans API and switched to Reflections. I implemented Maven and ran it with IntelliJ. Works well for me.
This is my first java program, so please excuse me if its too naive.
I have a 3rd party jar. I want to instantiate a class in the jar and be able to use its methods. Some details about the class in the jar:
Class File: rediff.inecom.catalog.product.CSVAPI
Constructor: CSVAPI()
Method: UpdateCSVAPI(key, csvpath)
Return: String
I have written the following program:
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.io.IOException;
class MyLoaderClass{
public void myLoaderFunction(){
File file = new File("vendorcatalogapi.jar");
try {
URL url = file.toURI().toURL();
URL[] urls = new URL[]{url};
ClassLoader cl = new URLClassLoader(urls);
Class cls = cl.loadClass("rediff.inecom.catalog.product.CSVAPI");
Object cls_object = cls.newInstance();
System.out.println(cls_object);
String output = cls_object.UpdateCSVAPI(12345,"myfile.csv");
System.out.println(output);
System.out.println("try");
}
catch (Exception e) {
System.out.println("catch");
e.printStackTrace();
}
}
public static void main(String args[]){
new MyLoaderClass().myLoaderFunction();
}
}
I am trying to compile it using:
javac -cp vendorcatalogapi.jar temp.java
But I am getting the following error:
temp.java:17: error: cannot find symbol
String output = cls_object.UpdateCSVAPI(12345,"myfile.csv");
^
symbol: method UpdateCSVAPI(int,String)
location: variable cls_object of type Object
1 error
Looks like the object is not correctly initialized. Please can someone help me with the correct way of doing it
If this is your first java program, then loading the class dynamically is probably overkill. Just use it normally and let the default class loader load it:
import java.io.File;
import java.net.URL;
import java.net.URLClassLoader;
import java.io.IOException;
import rediff.inecom.catalog.product.CSVAPI;
class MyFirstClass{
public void myFunction() {
CSVAPI cvsapi = new CSVAPI();
System.out.println(cvsapi);
String output = cvsapi.UpdateCSVAPI(12345,"myfile.csv");
System.out.println(output);
System.out.println("Success!");
}
public static void main(String args[]){
new MyFirstClass().myFunction();
}
}
Compile (note that the source code file name must match the class name):
javac -cp vendorcatalogapi.jar MyFirstClass.java
Run:
java -cp .:vendorcatalogapi.jar MyFirstClass (on Unix based)
java -cp .;vendorcatalogapi.jar MyFirstClass (on Windows)
You have to let the compiler know that cls_object is an instance of CSVAPI. If you don't, you can only use the object methods (toString, equals, etc.).
To do this, you can do the following:
rediff.inecom.catalog.product.CSVAPI cls_object = (rediff.inecom.catalog.product.CSVAPI) cls.newInstance();
Please, note that you need to have CSVAPI in your classpath!
Object class doesnt know the methods of rediff.inecom.catalog.product.CSVAPI class.
Class cls = cl.loadClass("rediff.inecom.catalog.product.CSVAPI");
Object cls_object = cls.newInstance();
So, explicit casting is required
rediff.inecom.catalog.product.CSVAPI object =
(rediff.inecom.catalog.product.CSVAPI) cls.newInstance();
will do the job.