Files
javademos/jep512/run.sh
T
Claude e0cb6e83e2 Add jep512 module: compact source files and instance main methods on JDK 25 and 27
Runnable sources, broken examples and captured transcripts for the ankurm.com JEP 512 article,
including the measured launch order and a private-constructor difference between 25 and 27.

Co-Authored-By: Claude Sonnet 5 <[email protected]>
Claude-Session: https://claude.ai/code/session_01TF9JWFvJSNm6HVzswzZU5a
2026-09-24 10:32:47 +00:00

114 lines
6.0 KiB
Bash
Executable File

#!/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"