Search code examples
javajava-9java-module

Patch Java 9 module with test-code to work with reflections


How do I add my tests to my production code at test-runtime so that both are in the same Java 9 module and can access each other using reflections?

I have tried so far:

  • Remove the Java 9 modularity (actually the module-info.java) → it worked perfectly, but is not what I'm looking for.
  • Move my tests to a dedicated module (and therefore also package) → it worked perfectly, but is not what I'm looking for. (I want my Unit-Tests near my code.)
  • Use --patch-module to virtually add another folder (to the one specified using --module-path) → it worked with "normal" code but not with reflections, it doesn't find the classes specified by --module-path.
    • Specify both my test and production code using --patch-module → it only finds classes in the folder I specify first.
    • Explicitly add --add-opens mymodule/mypackge=mymodule or ...=ALL-UNNAMED to open it for reflection → doesn't look to have any effect.

So my full test line is:

java \
  --patch-module com.stackoverflow.examplemodule=ModuleInfoTest:ModuleInfoExample \
  --module-path ModuleInfoExample \
  --add-opens com.stackoverflow.examplemodule/com.stackoverflow.examplepackage=com.stackoverflow.examplemodule \
  --add-opens com.stackoverflow.examplemodule/com.stackoverflow.examplepackage=ALL-UNNAMED \
  --module com.stackoverflow.examplemodule/com.stackoverflow.examplepackage.Main

I'm in a directory containing the following subdirectories and files:

  • ModuleInfoExample/module-info.class
  • ModuleInfoExample/com/stackoverflow/examplepackage/Main.class
  • ModuleInfoTest/com/stackoverflow/examplepackage/AnyClass.class

I'm using:

openjdk version "13.0.2" 2020-01-14
OpenJDK Runtime Environment (build 13.0.2+8)
OpenJDK 64-Bit Server VM (build 13.0.2+8, mixed mode, sharing)



As I learned from the approved answer, my problem was not really "access other classes", as I phrased it. But more like find them (by scanning classpath/modulepath). However this part is already answered in this other StackOverflow question.


Solution

  • From Oracle's Java 13 java command these are the two options you tried to use:

    --add-opens module/package=target-module(,target-module)*
    --patch-module module=file(;file)*
    

    but:

    • --add-opens doesn't help you because it opens up one module to other modules. You only have one module.
    • --patch-module must specify the directories (or jarfiles) you want to patch into the module. I noticed that there's a ; not a : like you used. It seems to me that you've been telling java to patch files from same the directory where your module is at with :ModuleInfoExample.

    You only need to add the files from ModuleInfoTest/ into your module. I created the structure from your Question and ran it:

    Compiling:

    javac -d target/ModuleInfoExample src/ModuleInfoExample/*.java src/ModuleInfoExample/com/stackoverflow/examplepackage/*.java
    javac -cp target/ModuleInfoExample -d target/ModuleInfoTest src/ModuleInfoTest/com/stackoverflow/examplepackage/*.java
    

    Running Main from module - no added classes:

    java --module-path target/ModuleInfoExample --module com.stackoverflow.examplemodule/com.stackoverflow.examplepackage.Main
    
    prints:
    Hello world - I'm private
    

    Running AnyClass from module - no added classes - exception expected

    java --module-path target/ModuleInfoExample --module com.stackoverflow.examplemodule/com.stackoverflow.examplepackage.AnyClass
    
    Error: Could not find or load main class com.stackoverflow.examplepackage.AnyClass in module com.stackoverflow.examplemodule
    

    Running AnyClass from module - adding AnyClass into the package:

    java --module-path target/ModuleInfoExample --patch-module com.stackoverflow.examplemodule=target/ModuleInfoTest --module com.stackoverflow.examplemodule/com.stackoverflow.examplepackage.AnyClass
    
    prints:
    Inside AnyClass - calling Main: Hello world - I'm private
    
      field.get() = I'm private
      field.get() = I'm not private anymore
    

    Total structure:

    >tree /f
    ..snip..
    C:.
    +---src
    ¦   +---ModuleInfoExample
    ¦   ¦   ¦   module-info.java
    ¦   ¦   ¦
    ¦   ¦   +---com
    ¦   ¦       +---stackoverflow
    ¦   ¦           +---examplepackage
    ¦   ¦                   Main.java
    ¦   ¦
    ¦   +---ModuleInfoTest
    ¦       +---com
    ¦           +---stackoverflow
    ¦               +---examplepackage
    ¦                       AnyClass.java
    ¦
    +---target
        +---ModuleInfoExample
        ¦   ¦   module-info.class
        ¦   ¦
        ¦   +---com
        ¦       +---stackoverflow
        ¦           +---examplepackage
        ¦                   Main.class
        ¦
        +---ModuleInfoTest
            +---com
                +---stackoverflow
                    +---examplepackage
                            AnyClass.class
    

    src\ModuleInfoExample\module-info.java:

    module com.stackoverflow.examplemodule {
    //  exports com.stackoverflow.examplepackage; // no need to export. Nothing is using this
    }
    

    src\ModuleInfoExample\com\stackoverflow\examplepackage\Main.java:

    package com.stackoverflow.examplepackage;
    
    public class Main {
      private String privateString = "I'm private";
    
      public static void main(String[] args) {
        new Main().hello();
      }
      public void hello(){
        System.out.println("Hello world - " + privateString);
      }
    }
    

    src\ModuleInfoTest\com\stackoverflow\examplepackage\AnyClass.java:

    package com.stackoverflow.examplepackage;
    
    import java.lang.reflect.Constructor;
    import java.lang.reflect.Field;
    
    public class AnyClass {
      public static void main(String[] args) {
        testhello();
        System.out.println();
        breakhello();
      }
    
      public static void testhello(){
        System.out.print("Inside AnyClass - calling Main: ");
        Main test = new Main();
        test.hello();
      }
    
      public static void breakhello(){
        try {
          // Not necessary - same package, but..
          Class<?> mainClass = Class.forName("com.stackoverflow.examplepackage.Main");
          Constructor<?> constructor = mainClass.getConstructor();
          Object main = constructor.newInstance();
    
          // Getting, printing and changing the field..
          Field field = mainClass.getDeclaredField("privateString");
          field.setAccessible(true);
          System.out.println("  field.get() = " + field.get(main));
          field.set(main,"I'm not private anymore");
          System.out.println("  field.get() = " + field.get(main));
    
        } catch (Exception e) {  // Sorry, all in one big bucket
           System.out.println("Error: " + e);
        }
      }
    }