#!/usr/bin/env bash # JEP 512 (Compact Source Files and Instance Main Methods): final in JDK 25, no --enable-preview. # Regenerates every file in output/. Needs JDK 25 and JDK 27; a JDK 21 is optional (for the "before" transcript). # JDK25=/path JDK27=/path [JDK21=/path] ./run.sh set -uo pipefail unset JAVA_TOOL_OPTIONS HERE="$(cd "$(dirname "$0")" && pwd)" JDK25="${JDK25:-/opt/jdks/jdk-25.0.4.1+1}"; JDK27="${JDK27:-/opt/jdks/jdk27}"; JDK21="${JDK21:-}" OUT="$HERE/output"; mkdir -p "$OUT"; B="$(mktemp -d)"; trap 'rm -rf "$B"' EXIT cd "$HERE" v() { "$1/bin/java" -version 2>&1 | sed -n 2p; } # runtime line only, no vendor banner noise # 01 - the source launcher: one command, no javac, no class file left behind { echo "\$ cat src/Hello.java"; cat src/Hello.java; echo echo "\$ java src/Hello.java (JDK 25: $(v $JDK25))"; "$JDK25/bin/java" src/Hello.java 2>&1 echo; echo "\$ java src/Hello.java (JDK 27: $(v $JDK27))"; "$JDK27/bin/java" src/Hello.java 2>&1 echo; echo "\$ ls src/*.class"; ls src/*.class 2>&1 | sed 's/^/ /' if [ -n "$JDK21" ]; then echo; echo "\$ java src/Hello.java (JDK 21: $(v $JDK21))"; "$JDK21/bin/java" src/Hello.java 2>&1 fi } > "$OUT/01-source-launcher.txt" # 02 - javac produces an ordinary class file; javap shows what the compiler invented { echo "\$ javac -d out src/Hello.java && java -cp out Hello" "$JDK25/bin/javac" -d "$B/o" src/Hello.java 2>&1 && "$JDK25/bin/java" -cp "$B/o" Hello 2>&1 echo; echo "\$ javap -p -cp out Hello"; "$JDK25/bin/javap" -p -cp "$B/o" Hello 2>&1 echo; echo "\$ javap -v -cp out Hello | grep -E 'major|flags'" "$JDK25/bin/javap" -v -cp "$B/o" Hello 2>&1 | grep -E 'major version|^ flags' | head -2 echo; echo "\$ java Reflect (asks the loaded class)" "$JDK25/bin/javac" -cp "$B/o" -d "$B/o" src/Reflect.java 2>&1 && "$JDK25/bin/java" -cp "$B/o" Reflect 2>&1 } > "$OUT/02-javac-and-javap.txt" # 03 - the class body: members, nested types, implicit imports { echo "\$ java src/Members.java"; "$JDK25/bin/java" src/Members.java 2>&1 echo; echo "\$ java src/WithArgs.java one two"; "$JDK25/bin/java" src/WithArgs.java one two 2>&1 echo; echo "\$ java src/InstanceMainInClass.java"; "$JDK25/bin/java" src/InstanceMainInClass.java 2>&1 echo; echo "\$ java src/NeedsImport.java (java.net.http is outside java.base: a normal import fixes it)"; "$JDK25/bin/java" src/NeedsImport.java 2>&1 echo; echo "\$ java src/NeedsModuleImport.java (or import the whole module)"; "$JDK25/bin/java" src/NeedsModuleImport.java 2>&1 echo; echo "\$ java multi/App.java (App.java + Greeter.java in one directory)"; "$JDK25/bin/java" multi/App.java 2>&1 } > "$OUT/03-members-and-imports.txt" # 04 - the IO helper { echo "\$ printf 'Ankur\n30\n' | java src/ReadLn.java"; printf 'Ankur\n30\n' | "$JDK25/bin/java" src/ReadLn.java 2>&1 echo; echo "\$ java src/IoInAnyClass.java"; "$JDK25/bin/java" src/IoInAnyClass.java 2>&1 echo; echo "\$ java src/StaticImportIO.java (import static java.lang.IO.* makes the bare form work)"; "$JDK25/bin/java" src/StaticImportIO.java 2>&1 echo; echo "\$ java broken/NoIoPrefix.java (JDK 27: same error as on 25)"; "$JDK27/bin/java" broken/NoIoPrefix.java 2>&1 | head -8 echo; echo "\$ printf '' | java src/ReadLn.java (stdin closed: what does readln return?)" printf '' | "$JDK25/bin/java" src/ReadLn.java 2>&1 } > "$OUT/04-io-helper.txt" # 05 - which main wins. Generate every pair of mains that can coexist and ask the launcher. gen() { # name, body printf 'class %s {\n%s\n}\n' "$1" "$2" > "$B/$1.java" } gen SS_i0 ' static void main(String[] a) { IO.println("static main(String[])"); } void main() { IO.println("instance main()"); }' gen S0_iS ' static void main() { IO.println("static main()"); } void main(String[] a) { IO.println("instance main(String[])"); }' gen SS_S0 ' static void main(String[] a) { IO.println("static main(String[])"); } static void main() { IO.println("static main()"); }' gen IS_I0 ' void main(String[] a) { IO.println("instance main(String[])"); } void main() { IO.println("instance main()"); }' { for j in "$JDK25" "$JDK27"; do echo "== $(v $j)" for c in SS_i0 S0_iS SS_S0 IS_I0; do printf '%-8s declares: %-58s -> ran: ' "$c" "$(grep -o '\(static \)\?void main([^)]*)' "$B/$c.java" | tr '\n' ' ')" "$j/bin/java" "$B/$c.java" 2>&1 | head -1 done echo "-- same four classes, compiled first and launched as a class (java -cp out NAME)" rm -rf "$B/cls"; mkdir -p "$B/cls"; "$j/bin/javac" -d "$B/cls" "$B"/SS_i0.java "$B"/S0_iS.java "$B"/SS_S0.java "$B"/IS_I0.java 2>&1 for c in SS_i0 S0_iS SS_S0 IS_I0; do printf '%-8s -> ran: ' "$c"; "$j/bin/java" -cp "$B/cls" "$c" 2>&1 | head -1 done echo done } > "$OUT/05-launch-order.txt" # 06 - every way to get it wrong, with the compiler's own words { for f in NoMain NoIoPrefix NotInJavaBase WithPackage Other WrongParam PrivateMain CtorWithArgs; do if [ "$f" = Other ]; then echo "\$ javac -d out broken/Other.java broken/Target.java (JDK 25; Target.java is a compact file)" "$JDK25/bin/javac" -d "$B/x" broken/Other.java broken/Target.java 2>&1 | head -12 echo; echo "\$ java broken/Other.java (JDK 25; source launcher, Target.java sits next to it)" "$JDK25/bin/java" broken/Other.java 2>&1 | head -12 else echo "\$ java broken/$f.java (JDK 25)"; "$JDK25/bin/java" broken/$f.java 2>&1 | head -12 fi echo done } > "$OUT/06-compile-errors.txt" # 07 - the one behaviour that differs between 25 and 27 { for j in "$JDK25" "$JDK27"; do echo "\$ java broken/PrivateCtor.java ($(v $j))" "$j/bin/java" broken/PrivateCtor.java 2>&1 | head -6; echo done } > "$OUT/07-private-constructor-25-vs-27.txt" # 08 - what java.lang.IO actually is { echo "\$ javap -p java.lang.IO"; "$JDK25/bin/javap" -p java.lang.IO 2>&1 echo; echo "\$ javap -p -c java.lang.IO | grep -E 'public static|System.out|readLine|IOError'" "$JDK25/bin/javap" -p -c java.lang.IO 2>&1 | grep -E 'public static|System.out|readLine|IOError|flush' } > "$OUT/08-io-class.txt" echo "wrote $OUT"