Java 27 and 26: runnable demos and captured output for every JEP, plus version lanes
Co-Authored-By: Claude Sonnet 5 <[email protected]> Claude-Session: https://claude.ai/code/session_01B38FGKKam5SCGgwgduVAh3
This commit is contained in:
@@ -0,0 +1,15 @@
|
||||
/**
|
||||
* Tiny assertion helper so every demo is self-checking: a claim that stops being true turns the
|
||||
* run red instead of quietly printing something different. Passing checks are echoed, so the
|
||||
* transcript shows exactly what was verified.
|
||||
*/
|
||||
final class Check {
|
||||
private Check() {}
|
||||
|
||||
static void that(boolean condition, String claim) {
|
||||
if (!condition) {
|
||||
throw new AssertionError("CHECK FAILED: " + claim);
|
||||
}
|
||||
System.out.println("CHECK ok : " + claim);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,45 @@
|
||||
import java.io.DataInputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.net.ServerSocket;
|
||||
import java.net.Socket;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* How big is the TLS ClientHello this JDK sends?
|
||||
*
|
||||
* <p>A plain TCP server accepts the connection, reads the first TLS record header (5 bytes: type,
|
||||
* version, 2-byte length) and then the record body, and reports the length. The client handshake
|
||||
* never completes, and does not need to. Post-quantum key shares are large: this is the number that
|
||||
* decides whether a hello still fits in one network packet. Explained in docs/04-post-quantum-tls.md.
|
||||
*/
|
||||
public class ClientHelloSize {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (ServerSocket listener = new ServerSocket(0, 1, InetAddress.getLoopbackAddress())) {
|
||||
Thread client = new Thread(() -> {
|
||||
try (SSLSocket s = (SSLSocket) SSLSocketFactory.getDefault()
|
||||
.createSocket(InetAddress.getLoopbackAddress(), listener.getLocalPort())) {
|
||||
s.setSoTimeout(1_000);
|
||||
s.startHandshake(); // will time out: nobody answers
|
||||
} catch (Exception expected) {
|
||||
// the server side closes without replying
|
||||
}
|
||||
});
|
||||
client.start();
|
||||
try (Socket raw = listener.accept()) {
|
||||
DataInputStream in = new DataInputStream(raw.getInputStream());
|
||||
int type = in.readUnsignedByte();
|
||||
int version = in.readUnsignedShort();
|
||||
int length = in.readUnsignedShort();
|
||||
byte[] body = new byte[length];
|
||||
in.readFully(body);
|
||||
System.out.printf("java.version = %s%n", System.getProperty("java.version"));
|
||||
System.out.printf("record type = 0x%02x (0x16 = handshake)%n", type);
|
||||
System.out.printf("ClientHello record = %d bytes (+5 byte record header)%n", length);
|
||||
System.out.printf("fits one TCP segment = %s (1460-byte MSS on a 1500-byte MTU)%n", length + 5 <= 1460 ? "yes" : "NO");
|
||||
}
|
||||
client.join();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
/** Does nothing for a second. Exists only so a JVM has arguments, properties and environment for JFR to record. */
|
||||
public class Idle {
|
||||
public static void main(String[] args) throws Exception {
|
||||
Thread.sleep(1_000);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,94 @@
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
/**
|
||||
* JEP 531, Lazy Constants (third preview), against the JDK 27 API.
|
||||
*
|
||||
* <p>A lazy constant is a value that is computed the first time somebody asks for it and never again,
|
||||
* even when many threads ask at once. The JVM can then treat it like a {@code final} field. What changed
|
||||
* since JDK 26: {@code orElse(...)} and {@code isInitialized()} are gone (see broken/Lazy26.java), and
|
||||
* {@code Set.ofLazy(...)} arrived next to {@code List.ofLazy} and {@code Map.ofLazy}.
|
||||
* Explained in docs/06-lazy-constants.md. Compile and run with --enable-preview.
|
||||
*/
|
||||
public class LazyDemo {
|
||||
|
||||
static final AtomicInteger INITIALISATIONS = new AtomicInteger();
|
||||
|
||||
/** The expensive thing: pretend this reads a file and parses it. */
|
||||
record Settings(String region, int poolSize) {}
|
||||
|
||||
static Settings loadSettings() {
|
||||
INITIALISATIONS.incrementAndGet();
|
||||
System.out.println(" (loading settings on " + Thread.currentThread().getName() + ")");
|
||||
return new Settings("ap-south-1", 16);
|
||||
}
|
||||
|
||||
static final LazyConstant<Settings> SETTINGS = LazyConstant.of(LazyDemo::loadSettings);
|
||||
|
||||
/** Identity hashes and lambda addresses change on every run; blank them so the transcript is stable. */
|
||||
static String tidy(Object o) {
|
||||
return o.toString().replaceAll("@[0-9a-f]+", "@...").replaceAll("/0x[0-9a-f]+", "/0x...");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
System.out.println("--- a lazy constant computes once, on first use");
|
||||
System.out.println("toString before first get(): " + tidy(SETTINGS));
|
||||
Settings first = SETTINGS.get();
|
||||
Settings second = SETTINGS.get();
|
||||
System.out.println("toString after first get(): " + tidy(SETTINGS));
|
||||
Check.that(first == second, "get() returns the same instance every time");
|
||||
Check.that(INITIALISATIONS.get() == 1, "the supplier ran exactly once");
|
||||
|
||||
System.out.println("--- and once even when 64 threads race for it");
|
||||
var raced = LazyConstant.of(() -> {
|
||||
INITIALISATIONS.incrementAndGet();
|
||||
return "computed";
|
||||
});
|
||||
int before = INITIALISATIONS.get();
|
||||
var start = new CountDownLatch(1);
|
||||
try (var pool = Executors.newVirtualThreadPerTaskExecutor()) {
|
||||
for (int i = 0; i < 64; i++) {
|
||||
pool.submit(() -> {
|
||||
start.await();
|
||||
return raced.get();
|
||||
});
|
||||
}
|
||||
start.countDown();
|
||||
}
|
||||
Check.that(INITIALISATIONS.get() - before == 1, "64 racing threads triggered exactly one initialisation");
|
||||
|
||||
System.out.println("--- List.ofLazy: each element is computed on first access");
|
||||
AtomicInteger listComputations = new AtomicInteger();
|
||||
List<String> squares = List.ofLazy(5, i -> {
|
||||
listComputations.incrementAndGet();
|
||||
return "square(" + i + ")=" + (i * i);
|
||||
});
|
||||
System.out.println("computed so far: " + listComputations.get());
|
||||
System.out.println("squares.get(3) = " + squares.get(3));
|
||||
System.out.println("squares.get(3) = " + squares.get(3) + " (second read)");
|
||||
Check.that(listComputations.get() == 1, "only element 3 was ever computed");
|
||||
|
||||
System.out.println("--- Map.ofLazy: the keys are fixed, the values are computed on first access");
|
||||
AtomicInteger mapComputations = new AtomicInteger();
|
||||
Map<String, Integer> lengths = Map.ofLazy(Set.of("alpha", "beta", "gamma"), k -> {
|
||||
mapComputations.incrementAndGet();
|
||||
return k.length();
|
||||
});
|
||||
System.out.println("lengths.get(\"gamma\") = " + lengths.get("gamma"));
|
||||
Check.that(mapComputations.get() == 1, "only the requested key's value was computed");
|
||||
|
||||
System.out.println("--- Set.ofLazy (new in 27): membership is decided by a predicate, on demand");
|
||||
AtomicInteger setComputations = new AtomicInteger();
|
||||
Set<Integer> primes = Set.ofLazy(Set.of(2, 3, 4, 5, 6, 7), n -> {
|
||||
setComputations.incrementAndGet();
|
||||
return n > 1 && java.util.stream.IntStream.rangeClosed(2, (int) Math.sqrt(n)).noneMatch(d -> n % d == 0);
|
||||
});
|
||||
System.out.println("primes.contains(7) = " + primes.contains(7) + ", primes.contains(6) = " + primes.contains(6));
|
||||
Check.that(primes.contains(7) && !primes.contains(6), "the lazy set answers membership using the predicate");
|
||||
System.out.println("primes = " + new java.util.TreeSet<>(primes));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.BinaryEncodable;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.PEM;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.PEMEncoder;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import javax.crypto.EncryptedPrivateKeyInfo;
|
||||
import java.util.Arrays;
|
||||
import java.util.Base64;
|
||||
|
||||
/**
|
||||
* JEP 538, PEM Encodings of Cryptographic Objects (third preview), against the JDK 27 API.
|
||||
*
|
||||
* <p>PEM is the "-----BEGIN ...-----" text format every key and certificate file on a Linux box is in.
|
||||
* Before this API, turning a key into PEM or back meant hand-rolled Base64 and header strings.
|
||||
* Note the interface name: JDK 26 called the common supertype {@code DEREncodable}; JDK 27 renamed it
|
||||
* {@code BinaryEncodable}. See broken/Pem26Style.java. Explained in docs/08-pem-api.md.
|
||||
* Compile and run with --enable-preview.
|
||||
*/
|
||||
public class PemDemo {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
KeyPairGenerator gen = KeyPairGenerator.getInstance("EC");
|
||||
gen.initialize(256);
|
||||
KeyPair pair = gen.generateKeyPair();
|
||||
|
||||
// 1. Encode: one call, no Base64 in sight.
|
||||
String publicPem = PEMEncoder.of().encodeToString(pair.getPublic());
|
||||
String privatePem = PEMEncoder.of().encodeToString(pair.getPrivate());
|
||||
System.out.println("--- public key, encoded");
|
||||
System.out.println(firstLines(publicPem, 1) + " ... " + lastLine(publicPem));
|
||||
System.out.println("--- private key, encoded");
|
||||
System.out.println(firstLines(privatePem, 1) + " ... " + lastLine(privatePem));
|
||||
Check.that(publicPem.startsWith("-----BEGIN PUBLIC KEY-----"), "a PublicKey encodes as BEGIN PUBLIC KEY");
|
||||
Check.that(privatePem.startsWith("-----BEGIN PRIVATE KEY-----"), "a PrivateKey encodes as BEGIN PRIVATE KEY (PKCS#8)");
|
||||
|
||||
// 2. Decode, asking for the type you expect.
|
||||
PublicKey publicBack = PEMDecoder.of().decode(publicPem, PublicKey.class);
|
||||
PrivateKey privateBack = PEMDecoder.of().decode(privatePem, PrivateKey.class);
|
||||
Check.that(publicBack.equals(pair.getPublic()), "the decoded public key equals the original");
|
||||
Check.that(privateBack.equals(pair.getPrivate()), "the decoded private key equals the original");
|
||||
|
||||
// 3. Decode without saying what you expect, and switch on what came back.
|
||||
System.out.println("--- decode(String) returns whatever the header says it is");
|
||||
for (String pem : new String[] {publicPem, privatePem}) {
|
||||
BinaryEncodable decoded = PEMDecoder.of().decode(pem);
|
||||
String kind = switch (decoded) {
|
||||
case PublicKey k -> "PublicKey (" + k.getAlgorithm() + ")";
|
||||
case PrivateKey k -> "PrivateKey (" + k.getAlgorithm() + ")";
|
||||
default -> decoded.getClass().getName();
|
||||
};
|
||||
System.out.println(pem.lines().findFirst().orElse("") + " -> " + kind);
|
||||
}
|
||||
|
||||
// 4. Encrypted private keys: the password goes on the encoder and the decoder.
|
||||
char[] password = "correct horse".toCharArray();
|
||||
String encryptedPem = PEMEncoder.of().withEncryption(password).encodeToString(pair.getPrivate());
|
||||
System.out.println("--- encrypted private key");
|
||||
System.out.println(firstLines(encryptedPem, 1));
|
||||
Check.that(encryptedPem.startsWith("-----BEGIN ENCRYPTED PRIVATE KEY-----"), "withEncryption produces ENCRYPTED PRIVATE KEY");
|
||||
|
||||
BinaryEncodable withoutPassword = PEMDecoder.of().decode(encryptedPem);
|
||||
System.out.println("decoded with no password: " + withoutPassword.getClass().getName());
|
||||
Check.that(withoutPassword instanceof EncryptedPrivateKeyInfo, "without a password you get the still-encrypted structure back");
|
||||
|
||||
PrivateKey decrypted = PEMDecoder.of().withDecryption(password).decode(encryptedPem, PrivateKey.class);
|
||||
Check.that(decrypted.equals(pair.getPrivate()), "with the password you get the original private key");
|
||||
|
||||
// 5. A PEM type the JDK does not know: it round-trips as a raw PEM object.
|
||||
// The trap: PEM's content is the Base64 TEXT, not the payload. Hand it raw bytes and they are written verbatim.
|
||||
byte[] payload = "hello, pem".getBytes(StandardCharsets.UTF_8);
|
||||
String wrong = PEMEncoder.of().encodeToString(new PEM("ANKURM DEMO", payload));
|
||||
System.out.println("--- PEM(type, byte[]) does NOT Base64-encode: the bytes go in as they are");
|
||||
System.out.println(wrong.strip());
|
||||
Check.that(wrong.contains("hello, pem"), "raw bytes appear verbatim between the header and footer");
|
||||
|
||||
String base64 = Base64.getEncoder().encodeToString(payload);
|
||||
String right = PEMEncoder.of().encodeToString(new PEM("ANKURM DEMO", base64));
|
||||
System.out.println("--- give it the Base64 text and it round-trips");
|
||||
System.out.println(right.strip());
|
||||
BinaryEncodable raw = PEMDecoder.of().decode(right);
|
||||
Check.that(raw instanceof PEM p && p.type().equals("ANKURM DEMO")
|
||||
&& new String(p.content(), StandardCharsets.US_ASCII).strip().equals(base64)
|
||||
&& Arrays.equals(p.decode(), payload),
|
||||
"type, Base64 text (content()) and decoded payload (decode()) all survive");
|
||||
}
|
||||
|
||||
static String firstLines(String s, int n) {
|
||||
return s.lines().limit(n).reduce((a, b) -> a + "\n" + b).orElse("");
|
||||
}
|
||||
|
||||
static String lastLine(String s) {
|
||||
return s.lines().reduce((a, b) -> b).orElse("");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* JEP 532, Primitive Types in Patterns, instanceof, and switch (fifth preview).
|
||||
*
|
||||
* <p>Until now patterns worked on reference types only. With this preview a pattern can name a
|
||||
* primitive type, and the test is "can this value be converted to that type WITHOUT losing
|
||||
* information?" Explained in docs/05-primitive-patterns.md. Compile and run with --enable-preview.
|
||||
*/
|
||||
public class PrimitivePatterns {
|
||||
|
||||
record Reading(int celsius) {}
|
||||
|
||||
public static void main(String[] args) {
|
||||
exactness();
|
||||
switchOnLong();
|
||||
switchOnObject();
|
||||
recordPattern();
|
||||
}
|
||||
|
||||
/** instanceof with a primitive type asks: is this an EXACT conversion? */
|
||||
static void exactness() {
|
||||
System.out.println("--- instanceof <primitive>: an exact-conversion test, not a cast");
|
||||
int small = 100, big = 300;
|
||||
boolean a = small instanceof byte;
|
||||
boolean b = big instanceof byte;
|
||||
System.out.println("100 instanceof byte = " + a);
|
||||
System.out.println("300 instanceof byte = " + b + " (a byte holds -128..127)");
|
||||
Check.that(a && !b, "100 converts to byte exactly, 300 does not");
|
||||
|
||||
if (small instanceof byte narrow) {
|
||||
System.out.println("binding: byte narrow = " + narrow);
|
||||
}
|
||||
|
||||
int precise = 16_777_217; // 2^24 + 1: the first int a float cannot hold
|
||||
boolean f1 = 16_777_216 instanceof float;
|
||||
boolean f2 = precise instanceof float;
|
||||
System.out.println("16_777_216 instanceof float = " + f1);
|
||||
System.out.println("16_777_217 instanceof float = " + f2 + " (float has a 24-bit significand)");
|
||||
Check.that(f1 && !f2, "int to float is exact up to 2^24 and lossy after");
|
||||
}
|
||||
|
||||
/** switch over a long, with a primitive pattern and a guard. */
|
||||
static String classify(long value) {
|
||||
return switch (value) {
|
||||
case 0L -> "zero";
|
||||
case 1L -> "one";
|
||||
case long v when v < 0 -> "negative (" + v + ")";
|
||||
case long v -> "positive (" + v + ")";
|
||||
};
|
||||
}
|
||||
|
||||
static void switchOnLong() {
|
||||
System.out.println("--- switch on a long, with pattern labels and a guard");
|
||||
for (long v : new long[] {0, 1, -7, 9_000_000_000L}) {
|
||||
System.out.println("classify(" + v + ") = " + classify(v));
|
||||
}
|
||||
Check.that(classify(0).equals("zero") && classify(-7).startsWith("negative"), "long switch dispatches on constants, then patterns");
|
||||
}
|
||||
|
||||
/** switch over an Object: primitive patterns match the boxed types they correspond to. */
|
||||
static String describe(Object o) {
|
||||
return switch (o) {
|
||||
case int i when i > 1000 -> "a large int " + i;
|
||||
case int i -> "an int " + i;
|
||||
case long l -> "a long " + l;
|
||||
case double d -> "a double " + d;
|
||||
case String s -> "a String of length " + s.length();
|
||||
default -> "something else: " + o;
|
||||
};
|
||||
}
|
||||
|
||||
static void switchOnObject() {
|
||||
System.out.println("--- switch on an Object: an Integer matches 'case int'");
|
||||
Object[] samples = {7, 5000, 7L, 2.5, "hello", 'x'};
|
||||
for (Object o : samples) {
|
||||
System.out.println("describe(" + o.getClass().getSimpleName() + " " + o + ") = " + describe(o));
|
||||
}
|
||||
Check.that(describe(5000).equals("a large int 5000"), "an Integer 5000 matches case int with a guard");
|
||||
}
|
||||
|
||||
/** Record patterns can now narrow a component: Reading.celsius is an int, the pattern asks for a byte. */
|
||||
static void recordPattern() {
|
||||
System.out.println("--- record pattern with a narrowing primitive component");
|
||||
Object[] readings = {new Reading(36), new Reading(4_000)};
|
||||
for (Object o : readings) {
|
||||
String verdict = switch (o) {
|
||||
case Reading(byte c) -> "fits in a byte: " + c;
|
||||
case Reading(int c) -> "needs a full int: " + c;
|
||||
default -> "not a reading";
|
||||
};
|
||||
System.out.println(o + " -> " + verdict);
|
||||
}
|
||||
Check.that(readings[0] instanceof Reading(byte _) && !(readings[1] instanceof Reading(byte _)),
|
||||
"Reading(byte c) matches 36 but not 4000");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,102 @@
|
||||
import java.time.Duration;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.StructuredTaskScope;
|
||||
import java.util.concurrent.StructuredTaskScope.Joiner;
|
||||
import java.util.concurrent.StructuredTaskScope.Subtask;
|
||||
|
||||
/**
|
||||
* JEP 533, Structured Concurrency (seventh preview), written against the JDK 27 API.
|
||||
*
|
||||
* <p>The headline change from JDK 26 is that {@code StructuredTaskScope} and {@code Joiner} gained a
|
||||
* third type parameter, {@code R_X}: the exception {@code join()} throws. That lets a scope throw YOUR
|
||||
* exception type rather than a generic {@code FailedException}. Compare broken/StructuredScope26.java,
|
||||
* which is JDK 26 code that no longer compiles. Explained in docs/07-structured-concurrency.md.
|
||||
*
|
||||
* <p>Compile and run with --enable-preview.
|
||||
*/
|
||||
public class StructuredDemo {
|
||||
|
||||
/** The application's own failure type: what R_X lets us surface. */
|
||||
static class OrderFailed extends Exception {
|
||||
OrderFailed(Throwable cause) {
|
||||
super("order failed: " + cause.getMessage(), cause);
|
||||
}
|
||||
}
|
||||
|
||||
static String fetchPrice() throws InterruptedException {
|
||||
Thread.sleep(50);
|
||||
return "price=42";
|
||||
}
|
||||
|
||||
static String fetchStock() throws InterruptedException {
|
||||
Thread.sleep(80);
|
||||
return "stock=7";
|
||||
}
|
||||
|
||||
static String failingCall() {
|
||||
throw new IllegalStateException("inventory service is down");
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
allSuccessful();
|
||||
yourOwnExceptionType();
|
||||
timeout();
|
||||
}
|
||||
|
||||
/** The default joiner: all subtasks must succeed, failure surfaces as ExecutionException. */
|
||||
static void allSuccessful() throws Exception {
|
||||
System.out.println("--- allSuccessfulOrThrow(): results as a List, failures as ExecutionException");
|
||||
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||
scope.fork(StructuredDemo::fetchPrice);
|
||||
scope.fork(StructuredDemo::fetchStock);
|
||||
List<String> results = scope.join();
|
||||
System.out.println("results = " + results);
|
||||
Check.that(results.equals(List.of("price=42", "stock=7")), "join() returns the subtask results in fork order");
|
||||
}
|
||||
|
||||
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||
scope.fork(StructuredDemo::fetchPrice);
|
||||
scope.fork(StructuredDemo::failingCall);
|
||||
scope.join();
|
||||
Check.that(false, "unreachable");
|
||||
} catch (ExecutionException e) {
|
||||
System.out.println("caught " + e.getClass().getName() + " with cause " + e.getCause());
|
||||
Check.that(e.getCause() instanceof IllegalStateException, "a failing subtask surfaces as ExecutionException(cause)");
|
||||
}
|
||||
}
|
||||
|
||||
/** New in 27: hand the joiner a function and join() throws YOUR type. */
|
||||
static void yourOwnExceptionType() throws InterruptedException {
|
||||
System.out.println("--- allSuccessfulOrThrow(Function): join() throws the exception type you choose");
|
||||
try (var scope = StructuredTaskScope.open(Joiner.<String, OrderFailed>allSuccessfulOrThrow(OrderFailed::new))) {
|
||||
scope.fork(StructuredDemo::fetchPrice);
|
||||
scope.fork(StructuredDemo::failingCall);
|
||||
scope.join(); // declared: throws OrderFailed, InterruptedException
|
||||
Check.that(false, "unreachable");
|
||||
} catch (OrderFailed e) {
|
||||
System.out.println("caught " + e.getClass().getSimpleName() + ": " + e.getMessage());
|
||||
Check.that(e.getCause() instanceof IllegalStateException, "the mapped exception wraps the original failure");
|
||||
}
|
||||
}
|
||||
|
||||
/** Timeouts are now a configuration option; a Joiner decides what a timeout means. */
|
||||
static void timeout() throws Exception {
|
||||
System.out.println("--- withTimeout(...): what happens when the scope runs out of time");
|
||||
try (var scope = StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow(),
|
||||
cfg -> cfg.withTimeout(Duration.ofMillis(100)))) {
|
||||
Subtask<String> slow = scope.fork(() -> {
|
||||
Thread.sleep(2_000);
|
||||
return "too late";
|
||||
});
|
||||
scope.join();
|
||||
Check.that(false, "unreachable");
|
||||
} catch (Exception e) {
|
||||
System.out.println("caught " + e.getClass().getName());
|
||||
System.out.println(" cause: " + e.getCause());
|
||||
Check.that(e.getCause() instanceof StructuredTaskScope.CancelledByTimeoutException
|
||||
|| e instanceof StructuredTaskScope.CancelledByTimeoutException,
|
||||
"a timeout is reported as CancelledByTimeoutException");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
import java.io.InputStream;
|
||||
import java.net.InetAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import javax.net.ssl.SSLServerSocket;
|
||||
import javax.net.ssl.SSLServerSocketFactory;
|
||||
import javax.net.ssl.SSLSocket;
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
/**
|
||||
* One end of a loopback TLS 1.3 handshake, so two different JDKs can be pointed at each other.
|
||||
*
|
||||
* <pre>
|
||||
* java TlsPeer server <portfile> listen on an ephemeral port, write the port to a file, serve one handshake
|
||||
* java TlsPeer client <port> connect and complete a handshake
|
||||
* </pre>
|
||||
*
|
||||
* The scripts run both with -Djavax.net.debug=ssl:handshake and read the negotiated key-exchange group
|
||||
* out of the debug log; no JDK API reports it directly. Explained in docs/04-post-quantum-tls.md.
|
||||
*/
|
||||
public class TlsPeer {
|
||||
|
||||
public static void main(String[] args) throws Exception {
|
||||
if (args[0].equals("server")) {
|
||||
SSLServerSocket server = (SSLServerSocket) SSLServerSocketFactory.getDefault()
|
||||
.createServerSocket(0, 1, InetAddress.getLoopbackAddress());
|
||||
Files.writeString(Path.of(args[1]), Integer.toString(server.getLocalPort()));
|
||||
try (SSLSocket socket = (SSLSocket) server.accept()) {
|
||||
socket.startHandshake();
|
||||
socket.getOutputStream().write('!');
|
||||
}
|
||||
server.close();
|
||||
} else {
|
||||
SSLSocket socket = (SSLSocket) SSLSocketFactory.getDefault()
|
||||
.createSocket(InetAddress.getLoopbackAddress(), Integer.parseInt(args[1]));
|
||||
socket.startHandshake();
|
||||
System.out.println("handshake complete: " + socket.getSession().getProtocol()
|
||||
+ " " + socket.getSession().getCipherSuite());
|
||||
InputStream in = socket.getInputStream();
|
||||
in.read();
|
||||
socket.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
import jdk.incubator.vector.FloatVector;
|
||||
import jdk.incubator.vector.VectorOperators;
|
||||
import jdk.incubator.vector.VectorSpecies;
|
||||
|
||||
/**
|
||||
* JEP 537, Vector API (twelfth incubator). The same source and the same class file run on JDK 26
|
||||
* (eleventh incubator, JEP 529) and JDK 27, because the public API did not change between them:
|
||||
* docs/output/50-vector-api-surface.txt is the javap diff that shows it. For what the API is and
|
||||
* how to use it well, see the companion guide to JEP 537 on ankurm.com.
|
||||
* Run with --add-modules jdk.incubator.vector.
|
||||
*/
|
||||
public class VectorDemo {
|
||||
static final VectorSpecies<Float> SPECIES = FloatVector.SPECIES_PREFERRED;
|
||||
|
||||
static float scalarDot(float[] a, float[] b) {
|
||||
float sum = 0;
|
||||
for (int i = 0; i < a.length; i++) {
|
||||
sum += a[i] * b[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
static float vectorDot(float[] a, float[] b) {
|
||||
int i = 0;
|
||||
FloatVector acc = FloatVector.zero(SPECIES);
|
||||
int bound = SPECIES.loopBound(a.length);
|
||||
for (; i < bound; i += SPECIES.length()) {
|
||||
acc = FloatVector.fromArray(SPECIES, a, i).fma(FloatVector.fromArray(SPECIES, b, i), acc);
|
||||
}
|
||||
float sum = acc.reduceLanes(VectorOperators.ADD);
|
||||
for (; i < a.length; i++) { // the tail the vector loop could not cover
|
||||
sum += a[i] * b[i];
|
||||
}
|
||||
return sum;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
System.out.println("java.version = " + System.getProperty("java.version"));
|
||||
System.out.println("preferred species = " + SPECIES + " (" + SPECIES.length() + " float lanes, machine dependent)");
|
||||
int n = 1_000_003; // deliberately not a multiple of the lane count
|
||||
float[] a = new float[n], b = new float[n];
|
||||
for (int i = 0; i < n; i++) {
|
||||
a[i] = (i % 7) * 0.5f;
|
||||
b[i] = (i % 5) * 0.25f;
|
||||
}
|
||||
float scalar = scalarDot(a, b);
|
||||
float vector = vectorDot(a, b);
|
||||
System.out.println("scalar dot = " + scalar);
|
||||
System.out.println("vector dot = " + vector);
|
||||
// Floating-point addition is not associative, so the two sums differ in the last bits; compare with a tolerance.
|
||||
Check.that(Math.abs(scalar - vector) / Math.abs(scalar) < 1e-3, "vector and scalar dot products agree to within 0.1%");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* A primitive pattern placed after the reference pattern that already matches every Integer.
|
||||
* The compiler rejects it on both 26 and 27. Driven by scripts/broken-on-27.sh.
|
||||
*/
|
||||
public class Dominated {
|
||||
static String describe(Object o) {
|
||||
return switch (o) {
|
||||
case Integer boxed -> "an Integer";
|
||||
case int primitive -> "an int"; // dominated: every Integer is already caught above
|
||||
default -> "something else";
|
||||
};
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,17 @@
|
||||
import java.lang.LazyConstant;
|
||||
|
||||
/**
|
||||
* JDK 26 code for the lazy constants preview. It compiles on 26 and fails on 27:
|
||||
* LazyConstant.orElse(...) and LazyConstant.isInitialized() were removed in the third preview.
|
||||
* Driven by scripts/broken-on-27.sh.
|
||||
*/
|
||||
public class Lazy26 {
|
||||
static final LazyConstant<String> GREETING = LazyConstant.of(() -> "hello");
|
||||
|
||||
public static void main(String[] args) {
|
||||
if (!GREETING.isInitialized()) {
|
||||
System.out.println("not yet: " + GREETING.orElse("<unset>"));
|
||||
}
|
||||
System.out.println(GREETING.get());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
import java.security.DEREncodable;
|
||||
import java.security.PEM;
|
||||
import java.security.PEMDecoder;
|
||||
import java.security.Provider;
|
||||
import java.security.Security;
|
||||
|
||||
/**
|
||||
* JDK 26 code for the PEM preview API. It compiles on 26 and fails on 27.
|
||||
* Three things moved between the second and third preview (JEP 524 -> JEP 538):
|
||||
* 1. DEREncodable was renamed BinaryEncodable
|
||||
* 2. PEMDecoder.withFactory(Provider) became withFactoriesOf(Provider)
|
||||
* 3. PEM was a record whose content() returned a String; it is now a class whose content() returns byte[]
|
||||
* Driven by scripts/broken-on-27.sh, which commits the compiler's own messages.
|
||||
*/
|
||||
public class Pem26Style {
|
||||
public static void main(String[] args) {
|
||||
String text = "-----BEGIN ANKURM DEMO-----\naGVsbG8=\n-----END ANKURM DEMO-----\n";
|
||||
DEREncodable decoded = PEMDecoder.of().decode(text); // 1
|
||||
Provider provider = Security.getProviders()[0];
|
||||
PEMDecoder viaProvider = PEMDecoder.of().withFactory(provider); // 2
|
||||
if (decoded instanceof PEM pem) {
|
||||
String base64Text = pem.content(); // 3
|
||||
System.out.println(pem.type() + " / " + base64Text);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,23 @@
|
||||
import java.util.List;
|
||||
import java.util.concurrent.StructuredTaskScope;
|
||||
import java.util.concurrent.StructuredTaskScope.Joiner;
|
||||
|
||||
/**
|
||||
* JDK 26 code for the structured concurrency preview. It compiles on 26 and fails on 27:
|
||||
* StructuredTaskScope gained a third type parameter (the exception join() throws), and
|
||||
* FailedException / TimeoutException no longer exist. Driven by scripts/broken-on-27.sh.
|
||||
*/
|
||||
public class StructuredScope26 {
|
||||
public static void main(String[] args) throws Exception {
|
||||
try (StructuredTaskScope<String, List<String>> scope =
|
||||
StructuredTaskScope.open(Joiner.<String>allSuccessfulOrThrow())) {
|
||||
scope.fork(() -> "price=42");
|
||||
List<String> results = scope.join();
|
||||
System.out.println(results);
|
||||
} catch (StructuredTaskScope.FailedException e) {
|
||||
System.out.println("a subtask failed: " + e.getCause());
|
||||
} catch (StructuredTaskScope.TimeoutException e) {
|
||||
System.out.println("timed out");
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user