diff --git a/pom.xml b/pom.xml index d0aa9a5c..4844f5e4 100644 --- a/pom.xml +++ b/pom.xml @@ -34,6 +34,12 @@ jol-core 0.17 + + + org.ow2.asm + asm + 9.9 + diff --git a/src/main/java/org/javademos/init/Java15.java b/src/main/java/org/javademos/init/Java15.java index d402cc4c..6869a656 100644 --- a/src/main/java/org/javademos/init/Java15.java +++ b/src/main/java/org/javademos/init/Java15.java @@ -4,6 +4,7 @@ import org.javademos.commons.IDemo; import org.javademos.java15.jep360.SealedClassesDemo; +import org.javademos.java15.jep371.HiddenClassesDemo; import org.javademos.java15.jep372.NashornRemovalDemo; import org.javademos.java15.jep375.InstanceofPatternMatchingSecondPreview; import org.javademos.java15.jep381.SolarisSparcRemovalDemo; @@ -21,6 +22,8 @@ public static ArrayList getDemos() { // JEP 360 java15DemoPool.add(new SealedClassesDemo()); + // JEP 371 + java15DemoPool.add(new HiddenClassesDemo()); // JEP 372 java15DemoPool.add(new NashornRemovalDemo()); // JEP 375 diff --git a/src/main/java/org/javademos/java15/jep371/HiddenClassesDemo.java b/src/main/java/org/javademos/java15/jep371/HiddenClassesDemo.java new file mode 100644 index 00000000..45d00a31 --- /dev/null +++ b/src/main/java/org/javademos/java15/jep371/HiddenClassesDemo.java @@ -0,0 +1,177 @@ +package org.javademos.java15.jep371; + +import java.lang.invoke.MethodHandles; + +import org.javademos.commons.IDemo; +import org.objectweb.asm.ClassWriter; +import org.objectweb.asm.MethodVisitor; +import org.objectweb.asm.Opcodes; + +/// Demo for JDK 15 feature JEP 371 - Hidden Classes. +/// +/// JEP history: +/// - JDK 15: [JEP 371 - Hidden Classes](https://openjdk.org/jeps/371) +/// +/// Hidden classes are classes that cannot be used directly by the bytecode of other classes. +/// They are intended for use by frameworks that generate classes at runtime and use them indirectly, via reflection. +/// A hidden class may be defined as a member of an access control nest, and may be unloaded independently of other classes. +/// +/// Key features: +/// - Not discoverable: cannot be linked by the JVM or discovered by Class.forName() +/// - Unloadable: can be unloaded even if their defining class loader is still reachable +/// - Access control: can be nestmates with other classes +/// - Use case: language implementations (lambdas, proxies, etc.) +/// +/// Further reading: +/// - [JEP 371 Specification](https://openjdk.org/jeps/371) +/// - [Hidden Classes Tutorial](https://www.baeldung.com/java-hidden-classes) +/// +/// @see java.lang.invoke.MethodHandles.Lookup#defineHiddenClass +/// @author alois.seckar@gmail.com +public class HiddenClassesDemo implements IDemo { + + @Override + public void demo() { + info(371); + + try { + // Generate bytecode for a simple class with a greet() method + byte[] classBytes = generateSimpleClass(); + + // Get a Lookup object for defining hidden classes + MethodHandles.Lookup lookup = MethodHandles.lookup(); + + // Define a hidden class from the bytecode + MethodHandles.Lookup hiddenClassLookup = lookup.defineHiddenClass( + classBytes, + true, // initialize the class + MethodHandles.Lookup.ClassOption.NESTMATE + ); + + // Get the Class object for the hidden class + Class hiddenClass = hiddenClassLookup.lookupClass(); + + System.out.println("✓ Hidden class created successfully!"); + System.out.println(" Class name: " + hiddenClass.getName()); + System.out.println(" Is hidden: " + hiddenClass.isHidden()); + System.out.println(" Simple name: " + hiddenClass.getSimpleName()); + + // Demonstrate that hidden classes cannot be discovered by Class.forName() + System.out.println("\n✓ Attempting to discover hidden class by name..."); + boolean discovered = canBeDiscovered(hiddenClass.getName()); + System.out.println(" Can be discovered by Class.forName(): " + discovered); + System.out.println(" (Expected: false - this is the key feature!)"); + + // Create an instance and invoke the greet() method + System.out.println("\n✓ Creating instance and invoking method..."); + Object instance = hiddenClass.getDeclaredConstructor().newInstance(); + var method = hiddenClass.getDeclaredMethod("greet"); + method.invoke(instance); + + // Demonstrate nest membership + System.out.println("\n✓ Demonstrating nest membership:"); + System.out.println(" Hidden class nest host: " + hiddenClass.getNestHost().getName()); + System.out.println(" This class nest host: " + HiddenClassesDemo.class.getNestHost().getName()); + System.out.println(" Are nest mates: " + hiddenClass.getNestHost().equals(HiddenClassesDemo.class.getNestHost())); + + System.out.println("\n✓ Hidden class demonstration completed successfully!"); + + } catch (Throwable e) { + System.err.println("✗ Error demonstrating hidden classes: " + e.getMessage()); + e.printStackTrace(); + } + } + + /// Generates bytecode for a simple class using ASM library. + /// This creates a class equivalent to: + /// ```java + /// public class HiddenExample { + /// public HiddenExample() {} + /// public void greet() { + /// System.out.println(" → Hello from Hidden Class!"); + /// } + /// } + /// ``` + /// + /// @return byte array containing the class file bytecode + private byte[] generateSimpleClass() { + ClassWriter cw = new ClassWriter(ClassWriter.COMPUTE_FRAMES); + + // Define the class: public class HiddenExample extends Object + cw.visit( + Opcodes.V15, // Java 15 + Opcodes.ACC_PUBLIC, // public class + "HiddenExample", // class name + null, // signature (null = not generic) + "java/lang/Object", // superclass + null // interfaces (none) + ); + + // Generate default constructor: public HiddenExample() + MethodVisitor constructor = cw.visitMethod( + Opcodes.ACC_PUBLIC, // public + "", // constructor name + "()V", // descriptor: no args, void return + null, // signature + null // exceptions + ); + constructor.visitCode(); + constructor.visitVarInsn(Opcodes.ALOAD, 0); // load 'this' + constructor.visitMethodInsn( + Opcodes.INVOKESPECIAL, + "java/lang/Object", + "", + "()V", + false + ); + constructor.visitInsn(Opcodes.RETURN); + constructor.visitMaxs(1, 1); + constructor.visitEnd(); + + // Generate greet() method: public void greet() + MethodVisitor greetMethod = cw.visitMethod( + Opcodes.ACC_PUBLIC, // public + "greet", // method name + "()V", // descriptor: no args, void return + null, // signature + null // exceptions + ); + greetMethod.visitCode(); + + // System.out.println(" → Hello from Hidden Class!"); + greetMethod.visitFieldInsn( + Opcodes.GETSTATIC, + "java/lang/System", + "out", + "Ljava/io/PrintStream;" + ); + greetMethod.visitLdcInsn(" → Hello from Hidden Class!"); + greetMethod.visitMethodInsn( + Opcodes.INVOKEVIRTUAL, + "java/io/PrintStream", + "println", + "(Ljava/lang/String;)V", + false + ); + greetMethod.visitInsn(Opcodes.RETURN); + greetMethod.visitMaxs(2, 1); + greetMethod.visitEnd(); + + cw.visitEnd(); + return cw.toByteArray(); + } + + /// Attempts to discover a class by name using Class.forName(). + /// Hidden classes should NOT be discoverable this way. + /// + /// @param className the name of the class to look up + /// @return true if the class can be found, false otherwise + private boolean canBeDiscovered(String className) { + try { + Class.forName(className); + return true; + } catch (ClassNotFoundException e) { + return false; + } + } +} diff --git a/src/main/resources/JDK15Info.json b/src/main/resources/JDK15Info.json index 884a07f4..6a4adf71 100644 --- a/src/main/resources/JDK15Info.json +++ b/src/main/resources/JDK15Info.json @@ -1,4 +1,11 @@ [ { + "jep": 371, + "info": { + "name": "JEP 371 - Hidden Classes", + "dscr": "Introduces hidden classes that cannot be used directly by bytecode of other classes and can be unloaded independently" + } + }, + { "jep": 372, "info": { "name": "JEP 372 - Remove the Nashorn JavaScript Engine",