Search code examples
javareflectionnetbeansjava-modulejava-16

java ModuleLayer : ModuleLayer.Controller add methods don't work


I am using Netbeans 12.5 and java 16

In an Java Modular Project I have 2 Modules

Modular-Testing(/*The Project Name*/)
|
|----Consumer(/*Module Name*/)
|    |
     |--classes(/*Folder Name*/)
        |
        |--main(/*package name*/)
           |
           |--CustomModuleTest.java(Main Class)    


     
|--Test_Module_A(/*Module Name*/)
    |
    |--classes(/*Folder Name*/)
        |
        |--package_a(/*package name*/)
           |
           |--ClassA.java  

CustomModuleTest.java

public static void main(String[] args)throws Exception
{
   Path baseDir=Path.of("C:/Users/Home/Documents/NetBeansProjects/Netbeans/Modular-Testing/build/modules/");
 
   //Create the configuration for these two modules with boot-layer configuration as it's parent
   Configuration level1=Configuration.resolve
   (
     ModuleFinder.of(baseDir.resolve("Consumer"),baseDir.resolve("Test_Module_A"))
    ,List.of(ModuleLayer.boot().configuration())
    ,ModuleFinder.of()
    ,List.of("Consumer","Test_Module_A")
   );   

   //create the module layer with only one system class loader and boot layer as parent
   ModuleLayer.Controller layer1=ModuleLayer.defineModulesWithOneLoader
   (
     level1
    ,List.of(ModuleLayer.boot())
    ,ClassLoader.getSystemClassLoader()
   ); 
   
   //this is the main purpose of this test. I want to call ClassA.callMe() via reflection after I have dynamically achieved the permissions for it by first
   //Making Consumer Module read(requires) Test_Module_A
   //Making Test_Module_A open it's package(package_a) to Consumer
   //With this consumer should have full reflective access to Test_Module_A(or that's what i had hoped but it didn't work)

                          //Require(Read)
   //Make Module Consumer--------------->Test_Module_A 
   layer1.addReads
   (
     layer1.layer().findModule("Consumer").get()
    ,layer1.layer().findModule("Test_Module_A").get()
   ); 
                                 //Open package_a
   //Make Module Test_Module_A-------------------->Consumer 
   layer1.addOpens
   (
    layer1.layer().findModule("Test_Module_A").get()
   ,"package_a"
   ,layer1.layer().findModule("Consumer").get()
   ); 
   
   //Do the actual Test
   Class targetClass=layer1.layer().findLoader("Test_Module_A").loadClass("package_a.ClassA");
   Method method=targetClass.getDeclaredMethod("callMe");
   method.trySetAccessible();
   method.invoke(null);  //<---------Crashes Here(It's a static method so no object reference)
}

ClassA does nothing

package package_a;

public class ClassA 
{
  private static void callMe()
  {
   System.out.println("Hooray You Called Me Using ModuleLayers!!!");
  }
}

module-info for both Consumer & Test_Module_A are empty

module Consumer{}
module Test_Module_A{}

the module-info of both these modules are empty because i want to dynamically add Opens/Exports using ModuleLayer.Controller

The class is located with no problems the method is made accessable using trySetAccessible() but as soon as i call invoke i get this error

Exception in thread "main" java.lang.IllegalAccessException: class main.CustomModuleTest (in module Consumer) cannot access class package_a.ClassA (in module Test_Module_A) because module Test_Module_A does not export package_a to module Consumer
    at java.base/jdk.internal.reflect.Reflection.newIllegalAccessException(Reflection.java:385)
    at java.base/java.lang.reflect.AccessibleObject.checkAccess(AccessibleObject.java:687)
    at java.base/java.lang.reflect.Method.invoke(Method.java:559)

The test is run from CustomModuleTest.java main method inside Consumer Module the addReads & addOpens method either don't work or i am just using it wrong

Any ideas?


Solution

  • You are creating a new module layer containing two modules and establishing access of one of these modules to the other. However, you actually want to establishing access for the currently running main method to one the these modules.

    The currently running main method can’t be part of the new module layer it just creates. Assuming standard launcher setup, it belongs to a Controller module loaded by the system class loader as part of the boot-layer.

    So, instead of granting access to a new module of the same name in the new layer, you have to change the code to grant access to the already loaded Controller module. Since the new Controller module of the new module layer serves no purpose then, you can remove it from the layer configuration.

    public static void main(String[] args) throws Exception {
       Path baseDir = Path.of(
         "C:/Users/Home/Documents/NetBeansProjects/Netbeans/Modular-Testing/build/modules/");
    
       // Create the configuration for the new module
       // with boot-layer configuration as it's parent
       Configuration level1=Configuration.resolve
       (
         ModuleFinder.of(baseDir.resolve("Test_Module_A")),
         List.of(ModuleLayer.boot().configuration()),
         ModuleFinder.of(), List.of("Test_Module_A")
       );
    
       //create the module layer with only one class loader and boot layer as parent
       var layer1Controller = ModuleLayer.defineModulesWithOneLoader(
         level1, List.of(ModuleLayer.boot()), ClassLoader.getSystemClassLoader()
       );
       var testModA = layer1Controller.layer().findModule("Test_Module_A").get();
       var myModule = CustomModuleTest.class.getModule();
    
       myModule.addReads(testModA); // add read edge
    
       layer1Controller.addOpens(testModA, "package_a", myModule); // open package_a
    
       //Do the actual Test
       Class<?> targetClass = testModA.getClassLoader().loadClass("package_a.ClassA");
       Method method = targetClass.getDeclaredMethod("callMe");
       method.setAccessible(true);
       method.invoke(null);
    }