diff --git a/proof/collections/CollectorsTeeing.java b/proof/collections/CollectorsTeeing.java new file mode 100755 index 0000000..b1e54dd --- /dev/null +++ b/proof/collections/CollectorsTeeing.java @@ -0,0 +1,21 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; +import static java.util.stream.Collectors.*; + +/// Proof: collectors-teeing +/// Source: content/collections/collectors-teeing.yaml +record Item(String name, double price) {} +record Stats(long count, double total) {} + +void main() { + var items = List.of(new Item("a", 10.0), new Item("b", 20.0)); + var result = items.stream().collect( + Collectors.teeing( + Collectors.counting(), + Collectors.summingDouble(Item::price), + Stats::new + ) + ); +} diff --git a/proof/collections/CopyingCollectionsImmutably.java b/proof/collections/CopyingCollectionsImmutably.java new file mode 100755 index 0000000..dedeacf --- /dev/null +++ b/proof/collections/CopyingCollectionsImmutably.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: copying-collections-immutably +/// Source: content/collections/copying-collections-immutably.yaml +void main() { + List original = new ArrayList<>(List.of("a", "b", "c")); + List copy = + List.copyOf(original); +} diff --git a/proof/collections/ImmutableListCreation.java b/proof/collections/ImmutableListCreation.java new file mode 100755 index 0000000..7b6c05a --- /dev/null +++ b/proof/collections/ImmutableListCreation.java @@ -0,0 +1,10 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: immutable-list-creation +/// Source: content/collections/immutable-list-creation.yaml +void main() { + List list = + List.of("a", "b", "c"); +} diff --git a/proof/collections/ImmutableMapCreation.java b/proof/collections/ImmutableMapCreation.java new file mode 100755 index 0000000..860d652 --- /dev/null +++ b/proof/collections/ImmutableMapCreation.java @@ -0,0 +1,10 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: immutable-map-creation +/// Source: content/collections/immutable-map-creation.yaml +void main() { + Map map = + Map.of("a", 1, "b", 2, "c", 3); +} diff --git a/proof/collections/ImmutableSetCreation.java b/proof/collections/ImmutableSetCreation.java new file mode 100755 index 0000000..152528f --- /dev/null +++ b/proof/collections/ImmutableSetCreation.java @@ -0,0 +1,10 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: immutable-set-creation +/// Source: content/collections/immutable-set-creation.yaml +void main() { + Set set = + Set.of("a", "b", "c"); +} diff --git a/proof/collections/MapEntryFactory.java b/proof/collections/MapEntryFactory.java new file mode 100755 index 0000000..15cbba1 --- /dev/null +++ b/proof/collections/MapEntryFactory.java @@ -0,0 +1,9 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: map-entry-factory +/// Source: content/collections/map-entry-factory.yaml +void main() { + var e = Map.entry("key", 42); +} diff --git a/proof/collections/ReverseListIteration.java b/proof/collections/ReverseListIteration.java new file mode 100755 index 0000000..828807d --- /dev/null +++ b/proof/collections/ReverseListIteration.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: reverse-list-iteration +/// Source: content/collections/reverse-list-iteration.yaml +void main() { + List list = List.of("a", "b", "c"); + for (String element : list.reversed()) { + System.out.println(element); + } +} diff --git a/proof/collections/SequencedCollections.java b/proof/collections/SequencedCollections.java new file mode 100755 index 0000000..8534f86 --- /dev/null +++ b/proof/collections/SequencedCollections.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: sequenced-collections +/// Source: content/collections/sequenced-collections.yaml +void main() { + List list = new ArrayList<>(List.of("a", "b", "c")); + var last = list.getLast(); + var first = list.getFirst(); + var reversed = list.reversed(); +} diff --git a/proof/collections/StreamToarrayTyped.java b/proof/collections/StreamToarrayTyped.java new file mode 100755 index 0000000..1a2cd42 --- /dev/null +++ b/proof/collections/StreamToarrayTyped.java @@ -0,0 +1,16 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; + +/// Proof: stream-toarray-typed +/// Source: content/collections/stream-toarray-typed.yaml +List getNames() { + return List.of("Alice", "Bob", "Charlie", "Dave"); +} + +void main() { + String[] arr = getNames().stream() + .filter(n -> n.length() > 3) + .toArray(String[]::new); +} diff --git a/proof/collections/UnmodifiableCollectors.java b/proof/collections/UnmodifiableCollectors.java new file mode 100755 index 0000000..26e5920 --- /dev/null +++ b/proof/collections/UnmodifiableCollectors.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; + +/// Proof: unmodifiable-collectors +/// Source: content/collections/unmodifiable-collectors.yaml +void main() { + Stream stream = Stream.of("a", "b", "c"); + List list = stream.toList(); +} diff --git a/proof/concurrency/CompletablefutureChaining.java b/proof/concurrency/CompletablefutureChaining.java new file mode 100755 index 0000000..6f9c0ad --- /dev/null +++ b/proof/concurrency/CompletablefutureChaining.java @@ -0,0 +1,16 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.concurrent.*; + +/// Proof: completablefuture-chaining +/// Source: content/concurrency/completablefuture-chaining.yaml +String fetchData() { return "data"; } +String transform(String data) { return data.toUpperCase(); } + +void main() { + CompletableFuture.supplyAsync( + this::fetchData + ) + .thenApply(this::transform) + .thenAccept(System.out::println); +} diff --git a/proof/concurrency/ConcurrentHttpVirtual.java b/proof/concurrency/ConcurrentHttpVirtual.java new file mode 100755 index 0000000..2deafc2 --- /dev/null +++ b/proof/concurrency/ConcurrentHttpVirtual.java @@ -0,0 +1,34 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.net.*; +import java.net.http.*; +import java.util.*; +import java.util.concurrent.*; + +/// Proof: concurrent-http-virtual +/// Source: content/concurrency/concurrent-http-virtual.yaml +HttpRequest req(String url) throws Exception { + return HttpRequest.newBuilder(new URI(url)).build(); +} + +void main() throws Exception { + var client = HttpClient.newHttpClient(); + var urls = List.of(); // empty — proof is compile-only + try (var exec = Executors + .newVirtualThreadPerTaskExecutor()) { + var results = urls.stream() + .map(u -> exec.submit(() -> { + try { + return client.send(req(u), + HttpResponse.BodyHandlers.ofString()).body(); + } catch (Exception e) { + throw new RuntimeException(e); + } + })) + .toList().stream() + .map(f -> { + try { return f.get(); } + catch (Exception e) { throw new RuntimeException(e); } + }).toList(); + } +} diff --git a/proof/concurrency/ExecutorTryWithResources.java b/proof/concurrency/ExecutorTryWithResources.java new file mode 100755 index 0000000..3b4d397 --- /dev/null +++ b/proof/concurrency/ExecutorTryWithResources.java @@ -0,0 +1,14 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.concurrent.*; + +/// Proof: executor-try-with-resources +/// Source: content/concurrency/executor-try-with-resources.yaml +void main() throws Exception { + Runnable task = () -> System.out.println("task"); + try (var exec = + Executors.newCachedThreadPool()) { + exec.submit(task); + } + // auto shutdown + await on close +} diff --git a/proof/concurrency/LockFreeLazyInit.java b/proof/concurrency/LockFreeLazyInit.java new file mode 100755 index 0000000..4bb27ac --- /dev/null +++ b/proof/concurrency/LockFreeLazyInit.java @@ -0,0 +1,28 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//JAVAC_OPTIONS --enable-preview --release 25 +//JAVA_OPTIONS --enable-preview +import java.util.function.*; + +/// Proof: lock-free-lazy-init +/// Source: content/concurrency/lock-free-lazy-init.yaml +/// +/// Note: The snippet calls INST.get() on a StableValue. In JDK 25, +/// StableValue.supplier() returns a Supplier whose get() provides the same +/// lock-free lazy initialization semantics. +class Config { + private static final Supplier INST = + StableValue.supplier(Config::load); + + static Config get() { + return INST.get(); + } + + static Config load() { + return null; // placeholder — real load reads from disk/DB + } +} + +void main() { + Config.get(); +} diff --git a/proof/concurrency/ProcessApi.java b/proof/concurrency/ProcessApi.java new file mode 100755 index 0000000..b4615a0 --- /dev/null +++ b/proof/concurrency/ProcessApi.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: process-api +/// Source: content/concurrency/process-api.yaml +void main() { + ProcessHandle ph = + ProcessHandle.current(); + long pid = ph.pid(); + ph.info().command() + .ifPresent(System.out::println); + ph.children().forEach( + c -> System.out.println(c.pid())); +} diff --git a/proof/concurrency/ScopedValues.java b/proof/concurrency/ScopedValues.java new file mode 100755 index 0000000..6dbfe90 --- /dev/null +++ b/proof/concurrency/ScopedValues.java @@ -0,0 +1,31 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.concurrent.*; + +/// Proof: scoped-values +/// Source: content/concurrency/scoped-values.yaml +record User(String name) {} +record Request(String path) {} + +class Handler { + static final ScopedValue CURRENT = + ScopedValue.newInstance(); + + User authenticate(Request req) { + return new User("Alice"); + } + + void handle(Request req) { + ScopedValue.where(CURRENT, + authenticate(req) + ).run(this::process); + } + + void process() { + // use CURRENT.get() in scope + } +} + +void main() { + new Handler().handle(new Request("/")); +} diff --git a/proof/concurrency/StableValues.java b/proof/concurrency/StableValues.java new file mode 100755 index 0000000..90055b3 --- /dev/null +++ b/proof/concurrency/StableValues.java @@ -0,0 +1,29 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//JAVAC_OPTIONS --enable-preview --release 25 +//JAVA_OPTIONS --enable-preview +import java.util.function.*; +import java.util.logging.*; + +/// Proof: stable-values +/// Source: content/concurrency/stable-values.yaml +/// +/// Note: The snippet calls logger.get() on a StableValue. In JDK 25, +/// StableValue.supplier() returns a Supplier whose get() provides the same +/// lazy, thread-safe initialization semantics. +class LoggerHolder { + private final Supplier logger = + StableValue.supplier(this::createLogger); + + Logger getLogger() { + return logger.get(); + } + + Logger createLogger() { + return Logger.getLogger(getClass().getName()); + } +} + +void main() { + new LoggerHolder().getLogger(); +} diff --git a/proof/concurrency/StructuredConcurrency.java b/proof/concurrency/StructuredConcurrency.java new file mode 100755 index 0000000..e6818c1 --- /dev/null +++ b/proof/concurrency/StructuredConcurrency.java @@ -0,0 +1,24 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//JAVAC_OPTIONS --enable-preview --release 25 +//JAVA_OPTIONS --enable-preview +import java.util.concurrent.*; + +/// Proof: structured-concurrency +/// Source: content/concurrency/structured-concurrency.yaml +record User(String name) {} +record Order(String id) {} +record Result(User user, Order order) {} + +User fetchUser() { return new User("Alice"); } +Order fetchOrder() { return new Order("o1"); } +Result combine(User u, Order o) { return new Result(u, o); } + +void main() throws Exception { + try (var scope = StructuredTaskScope.open()) { + var u = scope.fork(this::fetchUser); + var o = scope.fork(this::fetchOrder); + scope.join(); + combine(u.get(), o.get()); + } +} diff --git a/proof/concurrency/ThreadSleepDuration.java b/proof/concurrency/ThreadSleepDuration.java new file mode 100755 index 0000000..17c6207 --- /dev/null +++ b/proof/concurrency/ThreadSleepDuration.java @@ -0,0 +1,14 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.time.*; + +/// Proof: thread-sleep-duration +/// Source: content/concurrency/thread-sleep-duration.yaml +void main() throws InterruptedException { + Thread.sleep( + Duration.ofMillis(1) + ); + Thread.sleep( + Duration.ofMillis(1) + ); +} diff --git a/proof/concurrency/VirtualThreads.java b/proof/concurrency/VirtualThreads.java new file mode 100755 index 0000000..46c1255 --- /dev/null +++ b/proof/concurrency/VirtualThreads.java @@ -0,0 +1,9 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: virtual-threads +/// Source: content/concurrency/virtual-threads.yaml +void main() throws InterruptedException { + Thread.startVirtualThread(() -> { + System.out.println("hello"); + }).join(); +} diff --git a/proof/datetime/DateFormatting.java b/proof/datetime/DateFormatting.java new file mode 100755 index 0000000..57d2327 --- /dev/null +++ b/proof/datetime/DateFormatting.java @@ -0,0 +1,15 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.time.*; +import java.time.format.*; + +/// Proof: date-formatting +/// Source: content/datetime/date-formatting.yaml +void main() { + DateTimeFormatter fmt = + DateTimeFormatter.ofPattern( + "uuuu-MM-dd"); + String formatted = + LocalDate.now().format(fmt); + // Thread-safe, immutable +} diff --git a/proof/datetime/DurationAndPeriod.java b/proof/datetime/DurationAndPeriod.java new file mode 100755 index 0000000..293b8c1 --- /dev/null +++ b/proof/datetime/DurationAndPeriod.java @@ -0,0 +1,20 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.time.*; +import java.time.temporal.*; + +/// Proof: duration-and-period +/// Source: content/datetime/duration-and-period.yaml +void main() { + LocalDate date1 = LocalDate.of(2024, 1, 1); + LocalDate date2 = LocalDate.of(2025, 1, 1); + LocalTime time1 = LocalTime.of(8, 0); + LocalTime time2 = LocalTime.of(17, 0); + + long days = ChronoUnit.DAYS + .between(date1, date2); + Period period = Period.between( + date1, date2); + Duration elapsed = Duration.between( + time1, time2); +} diff --git a/proof/datetime/HexFormat.java b/proof/datetime/HexFormat.java new file mode 100755 index 0000000..3f07818 --- /dev/null +++ b/proof/datetime/HexFormat.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: hex-format +/// Source: content/datetime/hex-format.yaml +void main() { + byte byteValue = 0x48; + var hex = HexFormat.of() + .withUpperCase(); + String s = hex.toHexDigits( + byteValue); + byte[] bytes = + hex.parseHex("48656C6C6F"); +} diff --git a/proof/datetime/InstantPrecision.java b/proof/datetime/InstantPrecision.java new file mode 100755 index 0000000..ae5d9fe --- /dev/null +++ b/proof/datetime/InstantPrecision.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.time.*; + +/// Proof: instant-precision +/// Source: content/datetime/instant-precision.yaml +void main() { + // Microsecond/nanosecond precision + Instant now = Instant.now(); + // 2025-02-15T20:12:25.678901234Z + long nanos = now.getNano(); +} diff --git a/proof/datetime/JavaTimeBasics.java b/proof/datetime/JavaTimeBasics.java new file mode 100755 index 0000000..60510e9 --- /dev/null +++ b/proof/datetime/JavaTimeBasics.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.time.*; + +/// Proof: java-time-basics +/// Source: content/datetime/java-time-basics.yaml +void main() { + LocalDate date = LocalDate.of( + 2025, Month.JANUARY, 15); + LocalTime time = LocalTime.of(14, 30); + Instant now = Instant.now(); + // immutable, thread-safe +} diff --git a/proof/datetime/MathClamp.java b/proof/datetime/MathClamp.java new file mode 100755 index 0000000..ef35ba3 --- /dev/null +++ b/proof/datetime/MathClamp.java @@ -0,0 +1,10 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: math-clamp +/// Source: content/datetime/math-clamp.yaml +void main() { + int value = 150; + int clamped = + Math.clamp(value, 0, 100); + // value constrained to [0, 100] +} diff --git a/proof/enterprise/EjbTimerVsJakartaScheduler.java b/proof/enterprise/EjbTimerVsJakartaScheduler.java new file mode 100755 index 0000000..720b9ba --- /dev/null +++ b/proof/enterprise/EjbTimerVsJakartaScheduler.java @@ -0,0 +1,34 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 +//DEPS jakarta.annotation:jakarta.annotation-api:3.0.0 +//DEPS jakarta.enterprise.concurrent:jakarta.enterprise.concurrent-api:3.1.0 + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.annotation.Resource; +import jakarta.annotation.PostConstruct; +import jakarta.enterprise.concurrent.ManagedScheduledExecutorService; +import java.util.concurrent.TimeUnit; + +/// Proof: ejb-timer-vs-jakarta-scheduler +/// Source: content/enterprise/ejb-timer-vs-jakarta-scheduler.yaml +@ApplicationScoped +class ReportGenerator { + @Resource + ManagedScheduledExecutorService scheduler; + + @PostConstruct + public void init() { + scheduler.scheduleAtFixedRate( + this::generateReport, + 0, 24, TimeUnit.HOURS); + } + + public void generateReport() { + buildDailyReport(); + } + + void buildDailyReport() {} +} + +void main() {} diff --git a/proof/enterprise/EjbVsCdi.java b/proof/enterprise/EjbVsCdi.java new file mode 100755 index 0000000..6e168d7 --- /dev/null +++ b/proof/enterprise/EjbVsCdi.java @@ -0,0 +1,29 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 +//DEPS jakarta.transaction:jakarta.transaction-api:2.0.1 + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.transaction.Transactional; + +/// Proof: ejb-vs-cdi +/// Source: content/enterprise/ejb-vs-cdi.yaml +record Order(Object item) {} + +class InventoryService { + void reserve(Object item) {} +} + +@ApplicationScoped +class OrderService { + @Inject + private InventoryService inventory; + + @Transactional + public void placeOrder(Order order) { + inventory.reserve(order.item()); + } +} + +void main() {} diff --git a/proof/enterprise/JdbcResultsetVsJpaCriteria.java b/proof/enterprise/JdbcResultsetVsJpaCriteria.java new file mode 100755 index 0000000..f5bafc8 --- /dev/null +++ b/proof/enterprise/JdbcResultsetVsJpaCriteria.java @@ -0,0 +1,33 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.persistence:jakarta.persistence-api:3.2.0 + +import jakarta.persistence.*; +import jakarta.persistence.criteria.*; +import java.util.*; + +/// Proof: jdbc-resultset-vs-jpa-criteria +/// Source: content/enterprise/jdbc-resultset-vs-jpa-criteria.yaml +class User { + String status; + int age; +} + +class UserRepository { + @PersistenceContext + EntityManager em; + + public List findActiveAboveAge( + String status, int minAge) { + var cb = em.getCriteriaBuilder(); + var cq = + cb.createQuery(User.class); + var root = cq.from(User.class); + cq.select(root).where( + cb.equal(root.get("status"), status), + cb.greaterThan(root.get("age"), minAge)); + return em.createQuery(cq).getResultList(); + } +} + +void main() {} diff --git a/proof/enterprise/JdbcVsJooq.java b/proof/enterprise/JdbcVsJooq.java new file mode 100755 index 0000000..eaf2265 --- /dev/null +++ b/proof/enterprise/JdbcVsJooq.java @@ -0,0 +1,37 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS org.jooq:jooq:3.20.11 + +import org.jooq.*; +import org.jooq.impl.DSL; +import javax.sql.DataSource; +import java.util.*; + +/// Proof: jdbc-vs-jooq +/// Source: content/enterprise/jdbc-vs-jooq.yaml +class User { + Long id; String name; String email; +} + +// Simulating the generated jOOQ table fields (normally produced by jOOQ codegen) +class USERS { + static final Field DEPARTMENT = DSL.field(DSL.name("department"), String.class); + static final Field SALARY = DSL.field(DSL.name("salary"), Integer.class); + static final Field ID = DSL.field(DSL.name("id"), Long.class); + static final Field NAME = DSL.field(DSL.name("name"), String.class); + static final Field EMAIL = DSL.field(DSL.name("email"), String.class); + static final Table TABLE = DSL.table(DSL.name("users")); +} + +List findByDept(DataSource ds, String department, int minSalary) { + DSLContext dsl = DSL.using(ds, SQLDialect.POSTGRES); + + return dsl + .select(USERS.ID, USERS.NAME, USERS.EMAIL) + .from(USERS.TABLE) + .where(USERS.DEPARTMENT.eq(department) + .and(USERS.SALARY.gt(minSalary))) + .fetchInto(User.class); +} + +void main() {} diff --git a/proof/enterprise/JdbcVsJpa.java b/proof/enterprise/JdbcVsJpa.java new file mode 100755 index 0000000..a38cd00 --- /dev/null +++ b/proof/enterprise/JdbcVsJpa.java @@ -0,0 +1,31 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.persistence:jakarta.persistence-api:3.2.0 + +import jakarta.persistence.*; +import java.util.*; + +/// Proof: jdbc-vs-jpa +/// Source: content/enterprise/jdbc-vs-jpa.yaml +class User { + Long id; String name; +} + +class UserRepository { + @PersistenceContext + EntityManager em; + + public User findUser(Long id) { + return em.find(User.class, id); + } + + public List findByName(String name) { + return em.createQuery( + "SELECT u FROM User u WHERE u.name = :name", + User.class) + .setParameter("name", name) + .getResultList(); + } +} + +void main() {} diff --git a/proof/enterprise/JndiLookupVsCdiInjection.java b/proof/enterprise/JndiLookupVsCdiInjection.java new file mode 100755 index 0000000..91ee6e3 --- /dev/null +++ b/proof/enterprise/JndiLookupVsCdiInjection.java @@ -0,0 +1,32 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 +//DEPS jakarta.annotation:jakarta.annotation-api:3.0.0 + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.inject.Inject; +import jakarta.annotation.Resource; +import javax.sql.DataSource; +import java.sql.*; +import java.util.*; + +/// Proof: jndi-lookup-vs-cdi-injection +/// Source: content/enterprise/jndi-lookup-vs-cdi-injection.yaml +record Order(String id) {} + +@ApplicationScoped +class OrderService { + @Inject + @Resource(name = "jdbc/OrderDB") + DataSource ds; + + public List findAll() + throws SQLException { + try (Connection con = ds.getConnection()) { + // query orders + } + return List.of(); + } +} + +void main() {} diff --git a/proof/enterprise/JpaVsJakartaData.java b/proof/enterprise/JpaVsJakartaData.java new file mode 100755 index 0000000..dd9b385 --- /dev/null +++ b/proof/enterprise/JpaVsJakartaData.java @@ -0,0 +1,19 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.data:jakarta.data-api:1.0.1 + +import jakarta.data.repository.*; +import java.util.*; + +/// Proof: jpa-vs-jakarta-data +/// Source: content/enterprise/jpa-vs-jakarta-data.yaml +class User { + Long id; String name; +} + +@Repository +interface Users extends CrudRepository { + List findByName(String name); +} + +void main() {} diff --git a/proof/enterprise/JsfManagedBeanVsCdiNamed.java b/proof/enterprise/JsfManagedBeanVsCdiNamed.java new file mode 100755 index 0000000..000ec1a --- /dev/null +++ b/proof/enterprise/JsfManagedBeanVsCdiNamed.java @@ -0,0 +1,30 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 + +import jakarta.enterprise.context.SessionScoped; +import jakarta.inject.Inject; +import jakarta.inject.Named; +import java.io.*; + +/// Proof: jsf-managed-bean-vs-cdi-named +/// Source: content/enterprise/jsf-managed-bean-vs-cdi-named.yaml +interface UserService { + String findName(String id); +} + +@Named +@SessionScoped +class UserBean implements Serializable { + @Inject + private UserService userService; + + private String name; + + public String getName() { return name; } + public void setName(String name) { + this.name = name; + } +} + +void main() {} diff --git a/proof/enterprise/ManualTransactionVsDeclarative.java b/proof/enterprise/ManualTransactionVsDeclarative.java new file mode 100755 index 0000000..bdb3142 --- /dev/null +++ b/proof/enterprise/ManualTransactionVsDeclarative.java @@ -0,0 +1,35 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 +//DEPS jakarta.persistence:jakarta.persistence-api:3.2.0 +//DEPS jakarta.transaction:jakarta.transaction-api:2.0.1 + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.persistence.*; +import jakarta.transaction.Transactional; +import java.math.*; + +/// Proof: manual-transaction-vs-declarative +/// Source: content/enterprise/manual-transaction-vs-declarative.yaml +class Account { + Long id; BigDecimal balance; + Account debit(BigDecimal amount) { return this; } + Account credit(BigDecimal amount) { return this; } +} + +@ApplicationScoped +class AccountService { + @PersistenceContext + EntityManager em; + + @Transactional + public void transferFunds(Long from, Long to, + BigDecimal amount) { + var src = em.find(Account.class, from); + var dst = em.find(Account.class, to); + src.debit(amount); + dst.credit(amount); + } +} + +void main() {} diff --git a/proof/enterprise/MdbVsReactiveMessaging.java b/proof/enterprise/MdbVsReactiveMessaging.java new file mode 100755 index 0000000..cf74c2e --- /dev/null +++ b/proof/enterprise/MdbVsReactiveMessaging.java @@ -0,0 +1,25 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 +//DEPS org.eclipse.microprofile.reactive.messaging:microprofile-reactive-messaging-api:3.0.1 + +import jakarta.enterprise.context.ApplicationScoped; +import org.eclipse.microprofile.reactive.messaging.Incoming; + +/// Proof: mdb-vs-reactive-messaging +/// Source: content/enterprise/mdb-vs-reactive-messaging.yaml +record Order(String id) {} + +void fulfillOrder(Order order) {} + +@ApplicationScoped +class OrderProcessor { + @Incoming("orders") + public void process(Order order) { + // automatically deserialized from + // the "orders" channel + fulfillOrder(order); + } +} + +void main() {} diff --git a/proof/enterprise/ServletVsJaxrs.java b/proof/enterprise/ServletVsJaxrs.java new file mode 100755 index 0000000..1183bbb --- /dev/null +++ b/proof/enterprise/ServletVsJaxrs.java @@ -0,0 +1,22 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.ws.rs:jakarta.ws.rs-api:4.0.0 + +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; + +/// Proof: servlet-vs-jaxrs +/// Source: content/enterprise/servlet-vs-jaxrs.yaml +record User(String id) {} + +@Path("/users") +class UserResource { + @GET + @Produces(MediaType.APPLICATION_JSON) + public Response getUser( + @QueryParam("id") String id) { + return Response.ok(new User(id)).build(); + } +} + +void main() {} diff --git a/proof/enterprise/SingletonEjbVsCdiApplicationScoped.java b/proof/enterprise/SingletonEjbVsCdiApplicationScoped.java new file mode 100755 index 0000000..cfd1b85 --- /dev/null +++ b/proof/enterprise/SingletonEjbVsCdiApplicationScoped.java @@ -0,0 +1,34 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.enterprise:jakarta.enterprise.cdi-api:4.1.0 +//DEPS jakarta.annotation:jakarta.annotation-api:3.0.0 + +import jakarta.enterprise.context.ApplicationScoped; +import jakarta.annotation.PostConstruct; +import java.util.*; + +/// Proof: singleton-ejb-vs-cdi-application-scoped +/// Source: content/enterprise/singleton-ejb-vs-cdi-application-scoped.yaml +@ApplicationScoped +class ConfigCache { + private volatile Map cache; + + @PostConstruct + public void load() { + cache = loadFromDatabase(); + } + + public String get(String key) { + return cache.get(key); + } + + public void refresh() { + cache = loadFromDatabase(); + } + + private Map loadFromDatabase() { + return Map.of("key", "value"); + } +} + +void main() {} diff --git a/proof/enterprise/SoapVsJakartaRest.java b/proof/enterprise/SoapVsJakartaRest.java new file mode 100755 index 0000000..ba0a5c4 --- /dev/null +++ b/proof/enterprise/SoapVsJakartaRest.java @@ -0,0 +1,34 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS jakarta.ws.rs:jakarta.ws.rs-api:4.0.0 +//DEPS jakarta.inject:jakarta.inject-api:2.0.1 + +import jakarta.ws.rs.*; +import jakarta.ws.rs.core.*; +import jakarta.inject.Inject; + +/// Proof: soap-vs-jakarta-rest +/// Source: content/enterprise/soap-vs-jakarta-rest.yaml +class User { + String id; String name; + User(String id, String name) { this.id = id; this.name = name; } +} + +interface UserService { + User findById(String id); +} + +@Path("/users") +@Produces(MediaType.APPLICATION_JSON) +class UserResource { + @Inject + UserService userService; + + @GET + @Path("/{id}") + public User getUser(@PathParam("id") String id) { + return userService.findById(id); + } +} + +void main() {} diff --git a/proof/enterprise/SpringApiVersioning.java b/proof/enterprise/SpringApiVersioning.java new file mode 100755 index 0000000..08b5634 --- /dev/null +++ b/proof/enterprise/SpringApiVersioning.java @@ -0,0 +1,46 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS org.springframework:spring-webmvc:7.0.5 + +import org.springframework.context.annotation.Configuration; +import org.springframework.web.bind.annotation.*; +import org.springframework.web.servlet.config.annotation.*; + +/// Proof: spring-api-versioning +/// Source: content/enterprise/spring-api-versioning.yaml +record ProductDtoV1(Long id) {} +record ProductDtoV2(Long id, String name) {} + +// Configure versioning once +@Configuration +class WebConfig implements WebMvcConfigurer { + @Override + public void configureApiVersioning( + ApiVersionConfigurer config) { + config.useRequestHeader("X-API-Version"); + } +} + +interface ProductService { + ProductDtoV1 getV1(Long id); + ProductDtoV2 getV2(Long id); +} + +// Single controller, version per method +@RestController +@RequestMapping("/api/products") +class ProductController { + ProductService service; + + @GetMapping(value = "/{id}", version = "1") + public ProductDtoV1 getV1(@PathVariable Long id) { + return service.getV1(id); + } + + @GetMapping(value = "/{id}", version = "2") + public ProductDtoV2 getV2(@PathVariable Long id) { + return service.getV2(id); + } +} + +void main() {} diff --git a/proof/enterprise/SpringNullSafetyJspecify.java b/proof/enterprise/SpringNullSafetyJspecify.java new file mode 100755 index 0000000..77c602b --- /dev/null +++ b/proof/enterprise/SpringNullSafetyJspecify.java @@ -0,0 +1,36 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS org.jspecify:jspecify:1.0.0 + +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import java.util.*; + +/// Proof: spring-null-safety-jspecify +/// Source: content/enterprise/spring-null-safety-jspecify.yaml +record User(String name) {} + +interface UserRepository { + Optional findById(String id); + List findAll(); + User save(User user); +} + +@NullMarked +class UserService { + UserRepository repository; + + public @Nullable User findById(String id) { + return repository.findById(id).orElse(null); + } + + public List findAll() { + return repository.findAll(); + } + + public User save(User user) { + return repository.save(user); + } +} + +void main() {} diff --git a/proof/enterprise/SpringXmlConfigVsAnnotations.java b/proof/enterprise/SpringXmlConfigVsAnnotations.java new file mode 100755 index 0000000..161bffd --- /dev/null +++ b/proof/enterprise/SpringXmlConfigVsAnnotations.java @@ -0,0 +1,41 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS org.springframework.boot:spring-boot-autoconfigure:3.4.3 +//DEPS org.springframework.boot:spring-boot:3.4.3 +//DEPS org.springframework:spring-context:7.0.5 +//DEPS org.springframework:spring-jdbc:7.0.5 + +import org.springframework.boot.SpringApplication; +import org.springframework.boot.autoconfigure.SpringBootApplication; +import org.springframework.stereotype.Repository; +import org.springframework.stereotype.Service; +import org.springframework.jdbc.core.JdbcTemplate; + +/// Proof: spring-xml-config-vs-annotations +/// Source: content/enterprise/spring-xml-config-vs-annotations.yaml +@SpringBootApplication +class Application { + public static void main(String[] args) { + SpringApplication.run(Application.class, args); + } +} + +@Repository +class UserRepository { + private final JdbcTemplate jdbc; + + public UserRepository(JdbcTemplate jdbc) { + this.jdbc = jdbc; + } +} + +@Service +class UserService { + private final UserRepository repository; + + public UserService(UserRepository repository) { + this.repository = repository; + } +} + +void main() {} diff --git a/proof/errors/MultiCatch.java b/proof/errors/MultiCatch.java new file mode 100755 index 0000000..cc8293d --- /dev/null +++ b/proof/errors/MultiCatch.java @@ -0,0 +1,20 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.io.*; +import java.sql.*; +import java.text.*; + +/// Proof: multi-catch +/// Source: content/errors/multi-catch.yaml +void process() throws IOException, SQLException, ParseException {} +void log(Exception e) { System.err.println(e); } + +void main() { + try { + process(); + } catch (IOException + | SQLException + | ParseException e) { + log(e); + } +} diff --git a/proof/errors/NullInSwitch.java b/proof/errors/NullInSwitch.java new file mode 100755 index 0000000..7d806f1 --- /dev/null +++ b/proof/errors/NullInSwitch.java @@ -0,0 +1,19 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: null-in-switch +/// Source: content/errors/null-in-switch.yaml +enum Status { ACTIVE, PAUSED, STOPPED } + +String describe(Status status) { + return switch (status) { + case null -> "unknown"; + case ACTIVE -> "active"; + case PAUSED -> "paused"; + default -> "other"; + }; +} + +void main() { + describe(null); + describe(Status.ACTIVE); +} diff --git a/proof/errors/OptionalChaining.java b/proof/errors/OptionalChaining.java new file mode 100755 index 0000000..b4de1a8 --- /dev/null +++ b/proof/errors/OptionalChaining.java @@ -0,0 +1,19 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: optional-chaining +/// Source: content/errors/optional-chaining.yaml +record Address(String city) {} +record User(Address address) {} + +Address address(User u) { return u.address(); } +String city(Address a) { return a.city(); } + +void main() { + User user = null; + String city = Optional.ofNullable(user) + .map(User::address) + .map(Address::city) + .orElse("Unknown"); +} diff --git a/proof/errors/OptionalOrelsethrow.java b/proof/errors/OptionalOrelsethrow.java new file mode 100755 index 0000000..260804b --- /dev/null +++ b/proof/errors/OptionalOrelsethrow.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: optional-orelsethrow +/// Source: content/errors/optional-orelsethrow.yaml +void main() { + Optional optional = Optional.of("value"); + // Clear intent: throws NoSuchElementException if empty + String value = optional.orElseThrow(); +} diff --git a/proof/errors/RecordBasedErrors.java b/proof/errors/RecordBasedErrors.java new file mode 100755 index 0000000..3f36ad8 --- /dev/null +++ b/proof/errors/RecordBasedErrors.java @@ -0,0 +1,19 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.time.*; + +/// Proof: record-based-errors +/// Source: content/errors/record-based-errors.yaml +public record ApiError( + int code, + String message, + Instant timestamp +) { + public ApiError(int code, String msg) { + this(code, msg, Instant.now()); + } +} + +void main() { + var err = new ApiError(404, "not found"); +} diff --git a/proof/errors/RequireNonnullElse.java b/proof/errors/RequireNonnullElse.java new file mode 100755 index 0000000..387721e --- /dev/null +++ b/proof/errors/RequireNonnullElse.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: require-nonnull-else +/// Source: content/errors/require-nonnull-else.yaml +void main() { + String input = null; + String name = Objects + .requireNonNullElse( + input, "default" + ); +} diff --git a/proof/io/DeserializationFilters.java b/proof/io/DeserializationFilters.java new file mode 100755 index 0000000..1b9187f --- /dev/null +++ b/proof/io/DeserializationFilters.java @@ -0,0 +1,22 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.io.*; + +/// Proof: deserialization-filters +/// Source: content/io/deserialization-filters.yaml +void main() throws Exception { + var baos = new ByteArrayOutputStream(); + try (var oos = new ObjectOutputStream(baos)) { + oos.writeObject("test"); + } + var bais = new ByteArrayInputStream(baos.toByteArray()); + var ois = new ObjectInputStream(bais); + + ObjectInputFilter filter = + ObjectInputFilter.Config + .createFilter( + "java.lang.*;!*" + ); + ois.setObjectInputFilter(filter); + Object obj = ois.readObject(); +} diff --git a/proof/io/FileMemoryMapping.java b/proof/io/FileMemoryMapping.java new file mode 100755 index 0000000..3634313 --- /dev/null +++ b/proof/io/FileMemoryMapping.java @@ -0,0 +1,26 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.io.*; +import java.nio.*; +import java.nio.channels.*; +import java.nio.file.*; +import java.lang.foreign.*; + +/// Proof: file-memory-mapping +/// Source: content/io/file-memory-mapping.yaml +void main() throws Exception { + var path = Path.of(System.getProperty("java.io.tmpdir"), "proof-mmap.dat"); + Files.write(path, new byte[1024]); + FileChannel channel = + FileChannel.open(path, + StandardOpenOption.READ, + StandardOpenOption.WRITE); + try (Arena arena = Arena.ofShared()) { + MemorySegment segment = + channel.map( + FileChannel.MapMode.READ_WRITE, + 0, channel.size(), arena); + // No size limit + // ... + } // Deterministic cleanup +} diff --git a/proof/io/FilesMismatch.java b/proof/io/FilesMismatch.java new file mode 100755 index 0000000..c518d11 --- /dev/null +++ b/proof/io/FilesMismatch.java @@ -0,0 +1,15 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.nio.file.*; + +/// Proof: files-mismatch +/// Source: content/io/files-mismatch.yaml +void main() throws Exception { + var path1 = Path.of(System.getProperty("java.io.tmpdir"), "proof-a.txt"); + var path2 = Path.of(System.getProperty("java.io.tmpdir"), "proof-b.txt"); + Files.writeString(path1, "hello"); + Files.writeString(path2, "hello"); + long pos = Files.mismatch(path1, path2); + // -1 if identical + // otherwise: position of first difference +} diff --git a/proof/io/HttpClient.java b/proof/io/HttpClient.java new file mode 100755 index 0000000..c46a436 --- /dev/null +++ b/proof/io/HttpClient.java @@ -0,0 +1,17 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.net.*; +import java.net.http.*; + +/// Proof: http-client +/// Source: content/io/http-client.yaml +void main() throws Exception { + var client = HttpClient.newHttpClient(); + var request = HttpRequest.newBuilder() + .uri(URI.create("https://api.com/data")) + .build(); + // Compilation proof — not executed + // var response = client.send( + // request, BodyHandlers.ofString()); + // String body = response.body(); +} diff --git a/proof/io/InputstreamTransferto.java b/proof/io/InputstreamTransferto.java new file mode 100755 index 0000000..d8290a7 --- /dev/null +++ b/proof/io/InputstreamTransferto.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.io.*; +import java.io.InputStream; +import java.io.OutputStream; + +/// Proof: inputstream-transferto +/// Source: content/io/inputstream-transferto.yaml +void main() throws Exception { + InputStream input = new java.io.ByteArrayInputStream("hello".getBytes()); + OutputStream output = new java.io.ByteArrayOutputStream(); + input.transferTo(output); +} diff --git a/proof/io/IoClassConsoleIo.java b/proof/io/IoClassConsoleIo.java new file mode 100755 index 0000000..aaa3a53 --- /dev/null +++ b/proof/io/IoClassConsoleIo.java @@ -0,0 +1,8 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: io-class-console-io +/// Source: content/io/io-class-console-io.yaml +void main() { + // IO.readln prompts for input; here we just prove the API compiles + IO.println("Hello, " + "world"); +} diff --git a/proof/io/PathOf.java b/proof/io/PathOf.java new file mode 100755 index 0000000..7faa9d8 --- /dev/null +++ b/proof/io/PathOf.java @@ -0,0 +1,10 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.nio.file.*; + +/// Proof: path-of +/// Source: content/io/path-of.yaml +void main() { + var path = Path.of("src", "main", + "java", "App.java"); +} diff --git a/proof/io/ReadingFiles.java b/proof/io/ReadingFiles.java new file mode 100755 index 0000000..bc7ee7e --- /dev/null +++ b/proof/io/ReadingFiles.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.nio.file.*; + +/// Proof: reading-files +/// Source: content/io/reading-files.yaml +void main() throws Exception { + var path = Path.of(System.getProperty("java.io.tmpdir"), "proof-read.txt"); + Files.writeString(path, "content"); + String content = + Files.readString(path); +} diff --git a/proof/io/TryWithResourcesEffectivelyFinal.java b/proof/io/TryWithResourcesEffectivelyFinal.java new file mode 100755 index 0000000..c3ab164 --- /dev/null +++ b/proof/io/TryWithResourcesEffectivelyFinal.java @@ -0,0 +1,21 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS com.h2database:h2:2.3.232 + +import java.sql.*; + +/// Proof: try-with-resources-effectively-final +/// Source: content/io/try-with-resources-effectively-final.yaml +void use(Connection conn) throws SQLException {} + +Connection getConnection() throws SQLException { + return DriverManager.getConnection("jdbc:h2:mem:"); +} + +void main() throws Exception { + Connection conn = getConnection(); + // Use existing variable directly + try (conn) { + use(conn); + } +} diff --git a/proof/io/WritingFiles.java b/proof/io/WritingFiles.java new file mode 100755 index 0000000..1827ed6 --- /dev/null +++ b/proof/io/WritingFiles.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.nio.file.*; + +/// Proof: writing-files +/// Source: content/io/writing-files.yaml +void main() throws Exception { + String content = "hello world"; + Files.writeString( + Path.of(System.getProperty("java.io.tmpdir"), "proof-write.txt"), + content + ); +} diff --git a/proof/language/CompactCanonicalConstructor.java b/proof/language/CompactCanonicalConstructor.java new file mode 100755 index 0000000..2e1dcac --- /dev/null +++ b/proof/language/CompactCanonicalConstructor.java @@ -0,0 +1,17 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: compact-canonical-constructor +/// Source: content/language/compact-canonical-constructor.yaml +public record Person(String name, List pets) { + // Compact constructor + public Person { + Objects.requireNonNull(name); + pets = List.copyOf(pets); + } +} + +void main() { + var p = new Person("Alice", List.of("cat", "dog")); +} diff --git a/proof/language/CompactSourceFiles.java b/proof/language/CompactSourceFiles.java new file mode 100755 index 0000000..8b1b502 --- /dev/null +++ b/proof/language/CompactSourceFiles.java @@ -0,0 +1,7 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: compact-source-files +/// Source: content/language/compact-source-files.yaml +void main() { + IO.println("Hello, World!"); +} diff --git a/proof/language/DefaultInterfaceMethods.java b/proof/language/DefaultInterfaceMethods.java new file mode 100755 index 0000000..937d2fa --- /dev/null +++ b/proof/language/DefaultInterfaceMethods.java @@ -0,0 +1,26 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: default-interface-methods +/// Source: content/language/default-interface-methods.yaml +import java.io.*; + +public interface Logger { + default void log(String msg) { + System.out.println( + timestamp() + ": " + msg); + } + String timestamp(); +} + +// Multiple interfaces allowed +public class FileLogger + implements Logger, Closeable { + public String timestamp() { + return java.time.Instant.now().toString(); + } + public void close() {} +} + +void main() { + new FileLogger().log("hello"); +} diff --git a/proof/language/DiamondOperator.java b/proof/language/DiamondOperator.java new file mode 100755 index 0000000..dcfbd3f --- /dev/null +++ b/proof/language/DiamondOperator.java @@ -0,0 +1,16 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.function.*; + +/// Proof: diamond-operator +/// Source: content/language/diamond-operator.yaml +void main() { + Map> map = + new HashMap<>(); + // Java 9: diamond with anonymous classes + Predicate p = + new Predicate<>() { + public boolean test(String s) { return !s.isEmpty(); } + }; +} diff --git a/proof/language/ExhaustiveSwitch.java b/proof/language/ExhaustiveSwitch.java new file mode 100755 index 0000000..f2849f5 --- /dev/null +++ b/proof/language/ExhaustiveSwitch.java @@ -0,0 +1,22 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: exhaustive-switch +/// Source: content/language/exhaustive-switch.yaml +sealed interface Shape permits Circle, Rect {} +record Circle(double r) implements Shape {} +record Rect(double w, double h) implements Shape {} + +// sealed Shape permits Circle, Rect +double area(Shape s) { + return switch (s) { + case Circle c -> + Math.PI * c.r() * c.r(); + case Rect r -> + r.w() * r.h(); + }; // no default needed! +} + +void main() { + area(new Circle(1)); + area(new Rect(2, 3)); +} diff --git a/proof/language/FlexibleConstructorBodies.java b/proof/language/FlexibleConstructorBodies.java new file mode 100755 index 0000000..5c0cef5 --- /dev/null +++ b/proof/language/FlexibleConstructorBodies.java @@ -0,0 +1,23 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: flexible-constructor-bodies +/// Source: content/language/flexible-constructor-bodies.yaml +class Shape { + final double width, height; + Shape(double width, double height) { + this.width = width; + this.height = height; + } +} + +class Square extends Shape { + Square(double side) { + if (side <= 0) + throw new IllegalArgumentException("bad"); + super(side, side); + } +} + +void main() { + new Square(5); +} diff --git a/proof/language/GuardedPatterns.java b/proof/language/GuardedPatterns.java new file mode 100755 index 0000000..633c362 --- /dev/null +++ b/proof/language/GuardedPatterns.java @@ -0,0 +1,24 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: guarded-patterns +/// Source: content/language/guarded-patterns.yaml +sealed interface Shape permits Circle, Other {} +record Circle(double radius) implements Shape {} +record Other() implements Shape {} + +String describe(Shape shape) { + return switch (shape) { + case Circle c + when c.radius() > 10 + -> "large circle"; + case Circle c + -> "small circle"; + default -> "not a circle"; + }; +} + +void main() { + describe(new Circle(5)); + describe(new Circle(15)); + describe(new Other()); +} diff --git a/proof/language/MarkdownJavadocComments.java b/proof/language/MarkdownJavadocComments.java new file mode 100755 index 0000000..cd64a41 --- /dev/null +++ b/proof/language/MarkdownJavadocComments.java @@ -0,0 +1,23 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: markdown-javadoc-comments +/// Source: content/language/markdown-javadoc-comments.yaml +record User(int id, String name) {} + +/// Returns the `User` with +/// the given ID. +/// +/// Example: +/// ```java +/// var user = findUser(123); +/// ``` +/// +/// @param id the user ID +/// @return the user +public User findUser(int id) { + return new User(id, "Alice"); +} + +void main() { + findUser(1); +} diff --git a/proof/language/ModuleImportDeclarations.java b/proof/language/ModuleImportDeclarations.java new file mode 100755 index 0000000..c45875c --- /dev/null +++ b/proof/language/ModuleImportDeclarations.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import module java.base; + +/// Proof: module-import-declarations +/// Source: content/language/module-import-declarations.yaml +void main() { + // All of java.util, java.io, java.nio + // etc. available in one line + var list = new ArrayList(); + list.add("hello"); +} diff --git a/proof/language/PatternMatchingInstanceof.java b/proof/language/PatternMatchingInstanceof.java new file mode 100755 index 0000000..32571b2 --- /dev/null +++ b/proof/language/PatternMatchingInstanceof.java @@ -0,0 +1,10 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: pattern-matching-instanceof +/// Source: content/language/pattern-matching-instanceof.yaml +void main() { + Object obj = "Hello, world!"; + if (obj instanceof String s) { + System.out.println(s.length()); + } +} diff --git a/proof/language/PatternMatchingSwitch.java b/proof/language/PatternMatchingSwitch.java new file mode 100755 index 0000000..ee7f4ae --- /dev/null +++ b/proof/language/PatternMatchingSwitch.java @@ -0,0 +1,18 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: pattern-matching-switch +/// Source: content/language/pattern-matching-switch.yaml +String format(Object obj) { + return switch (obj) { + case Integer i -> "int: " + i; + case Double d -> "double: " + d; + case String s -> "str: " + s; + default -> "unknown"; + }; +} + +void main() { + format(42); + format(3.14); + format("hi"); +} diff --git a/proof/language/PrimitiveTypesInPatterns.java b/proof/language/PrimitiveTypesInPatterns.java new file mode 100755 index 0000000..f40cf11 --- /dev/null +++ b/proof/language/PrimitiveTypesInPatterns.java @@ -0,0 +1,21 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//JAVAC_OPTIONS --enable-preview --release 25 +//JAVA_OPTIONS --enable-preview +/// Proof: primitive-types-in-patterns +/// Source: content/language/primitive-types-in-patterns.yaml +String classify(int code) { + return switch (code) { + case int c when c >= 200 + && c < 300 -> "success"; + case int c when c >= 400 + && c < 500 -> "client error"; + default -> "other"; + }; +} + +void main() { + classify(200); + classify(404); + classify(500); +} diff --git a/proof/language/PrivateInterfaceMethods.java b/proof/language/PrivateInterfaceMethods.java new file mode 100755 index 0000000..499d817 --- /dev/null +++ b/proof/language/PrivateInterfaceMethods.java @@ -0,0 +1,22 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: private-interface-methods +/// Source: content/language/private-interface-methods.yaml +interface Logger { + private String format(String lvl, String msg) { + return "[" + lvl + "] " + timestamp() + msg; + } + default void logInfo(String msg) { + System.out.println(format("INFO", msg)); + } + default void logWarn(String msg) { + System.out.println(format("WARN", msg)); + } + String timestamp(); +} + +void main() { + Logger l = () -> "now"; + l.logInfo("started"); + l.logWarn("careful"); +} diff --git a/proof/language/RecordPatterns.java b/proof/language/RecordPatterns.java new file mode 100755 index 0000000..d4e5cbf --- /dev/null +++ b/proof/language/RecordPatterns.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: record-patterns +/// Source: content/language/record-patterns.yaml +record Point(int x, int y) {} + +void main() { + Object obj = new Point(3, 4); + if (obj instanceof Point(int x, int y)) { + System.out.println(x + y); + } +} diff --git a/proof/language/RecordsForDataClasses.java b/proof/language/RecordsForDataClasses.java new file mode 100755 index 0000000..da5ae2a --- /dev/null +++ b/proof/language/RecordsForDataClasses.java @@ -0,0 +1,9 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: records-for-data-classes +/// Source: content/language/records-for-data-classes.yaml +public record Point(int x, int y) {} + +void main() { + var p = new Point(1, 2); +} diff --git a/proof/language/SealedClasses.java b/proof/language/SealedClasses.java new file mode 100755 index 0000000..d7bb804 --- /dev/null +++ b/proof/language/SealedClasses.java @@ -0,0 +1,14 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: sealed-classes +/// Source: content/language/sealed-classes.yaml +public sealed interface Shape + permits Circle, Rect {} +public record Circle(double r) + implements Shape {} +public record Rect(double w, double h) + implements Shape {} + +void main() { + Shape s = new Circle(5); +} diff --git a/proof/language/StaticMembersInInnerClasses.java b/proof/language/StaticMembersInInnerClasses.java new file mode 100755 index 0000000..92e2f18 --- /dev/null +++ b/proof/language/StaticMembersInInnerClasses.java @@ -0,0 +1,20 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: static-members-in-inner-classes +/// Source: content/language/static-members-in-inner-classes.yaml +class Library { + // Can be inner class with statics + class Book { + static int globalBookCount; + + Book() { + Book.globalBookCount++; + } + } +} + +void main() { + // Usage + var lib = new Library(); + var book = lib.new Book(); +} diff --git a/proof/language/StaticMethodsInInterfaces.java b/proof/language/StaticMethodsInInterfaces.java new file mode 100755 index 0000000..27ef855 --- /dev/null +++ b/proof/language/StaticMethodsInInterfaces.java @@ -0,0 +1,20 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: static-methods-in-interfaces +/// Source: content/language/static-methods-in-interfaces.yaml +public interface Validator { + boolean validate(String s); + + static boolean isBlank(String s) { + return s == null || + s.trim().isEmpty(); + } +} + +void main() { + String input = ""; + // Usage + if (Validator.isBlank(input)) { + System.out.println("blank"); + } +} diff --git a/proof/language/SwitchExpressions.java b/proof/language/SwitchExpressions.java new file mode 100755 index 0000000..e173a5e --- /dev/null +++ b/proof/language/SwitchExpressions.java @@ -0,0 +1,15 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: switch-expressions +/// Source: content/language/switch-expressions.yaml +enum Day { MONDAY, TUESDAY, WEDNESDAY, + THURSDAY, FRIDAY, SATURDAY, SUNDAY } + +void main() { + Day day = Day.MONDAY; + String msg = switch (day) { + case MONDAY -> "Start"; + case FRIDAY -> "End"; + default -> "Mid"; + }; +} diff --git a/proof/language/TextBlocksForMultilineStrings.java b/proof/language/TextBlocksForMultilineStrings.java new file mode 100755 index 0000000..27ad51c --- /dev/null +++ b/proof/language/TextBlocksForMultilineStrings.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: text-blocks-for-multiline-strings +/// Source: content/language/text-blocks-for-multiline-strings.yaml +void main() { + String json = """ + { + "name": "Duke", + "age": 30 + }"""; +} diff --git a/proof/language/TypeInferenceWithVar.java b/proof/language/TypeInferenceWithVar.java old mode 100644 new mode 100755 index 9ee43fb..cfa1163 --- a/proof/language/TypeInferenceWithVar.java +++ b/proof/language/TypeInferenceWithVar.java @@ -1,3 +1,5 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ import java.util.*; /// Proof: type-inference-with-var diff --git a/proof/language/UnnamedVariables.java b/proof/language/UnnamedVariables.java new file mode 100755 index 0000000..6b46305 --- /dev/null +++ b/proof/language/UnnamedVariables.java @@ -0,0 +1,21 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: unnamed-variables +/// Source: content/language/unnamed-variables.yaml +void process(Object value) {} +void log(String msg) {} + +void main() { + String input = "123"; + Map map = Map.of("a", 1); + try { + Integer.parseInt(input); + } catch (Exception _) { + log("parse failed"); + } + map.forEach((_, value) -> { + process(value); + }); +} diff --git a/proof/security/KeyDerivationFunctions.java b/proof/security/KeyDerivationFunctions.java new file mode 100755 index 0000000..c93c604 --- /dev/null +++ b/proof/security/KeyDerivationFunctions.java @@ -0,0 +1,23 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import javax.crypto.*; +import javax.crypto.spec.*; +import java.security.*; + +/// Proof: key-derivation-functions +/// Source: content/security/key-derivation-functions.yaml +void main() throws Exception { + var kdf = KDF.getInstance("HKDF-SHA256"); + byte[] inputKeyBytes = new byte[32]; + byte[] salt = new byte[16]; + byte[] info = "context".getBytes(); + var inputKey = new SecretKeySpec(inputKeyBytes, "HKDF"); + SecretKey key = kdf.deriveKey( + "AES", + HKDFParameterSpec + .ofExtract() + .addIKM(inputKey) + .addSalt(salt) + .thenExpand(info, 32) + ); +} diff --git a/proof/security/PemEncoding.java b/proof/security/PemEncoding.java new file mode 100755 index 0000000..d7d2459 --- /dev/null +++ b/proof/security/PemEncoding.java @@ -0,0 +1,20 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//JAVAC_OPTIONS --enable-preview --release 25 +//JAVA_OPTIONS --enable-preview +import java.security.*; + +/// Proof: pem-encoding +/// Source: content/security/pem-encoding.yaml +void main() throws Exception { + KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA"); + kpg.initialize(2048); + KeyPair kp = kpg.generateKeyPair(); + + // Encode to PEM + String pem = PEMEncoder.of() + .encodeToString(kp.getPublic()); + // Decode from PEM + var cert = PEMDecoder.of() + .decode(pem); +} diff --git a/proof/security/RandomGenerator.java b/proof/security/RandomGenerator.java new file mode 100755 index 0000000..b79adeb --- /dev/null +++ b/proof/security/RandomGenerator.java @@ -0,0 +1,15 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.random.*; + +/// Proof: random-generator +/// Source: content/security/random-generator.yaml +void main() { + // Algorithm-agnostic via factory + var rng = RandomGenerator.of("L64X128MixRandom"); + int value = rng.nextInt(100); + + // Or get a splittable generator + var rng2 = RandomGeneratorFactory + .of("L64X128MixRandom").create(); +} diff --git a/proof/security/StrongRandom.java b/proof/security/StrongRandom.java new file mode 100755 index 0000000..b2de0bb --- /dev/null +++ b/proof/security/StrongRandom.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.security.*; + +/// Proof: strong-random +/// Source: content/security/strong-random.yaml +void main() throws Exception { + // Platform's strongest algorithm + SecureRandom random = + SecureRandom.getInstanceStrong(); + byte[] bytes = new byte[32]; + random.nextBytes(bytes); +} diff --git a/proof/security/TlsDefault.java b/proof/security/TlsDefault.java new file mode 100755 index 0000000..58c72ae --- /dev/null +++ b/proof/security/TlsDefault.java @@ -0,0 +1,14 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.net.http.*; +import javax.net.ssl.*; + +/// Proof: tls-default +/// Source: content/security/tls-default.yaml +void main() throws Exception { + // TLS 1.3 is the default! + var client = HttpClient.newBuilder() + .sslContext(SSLContext.getDefault()) + .build(); + // Already using TLS 1.3 +} diff --git a/proof/streams/CollectorsFlatmapping.java b/proof/streams/CollectorsFlatmapping.java new file mode 100755 index 0000000..db7adc8 --- /dev/null +++ b/proof/streams/CollectorsFlatmapping.java @@ -0,0 +1,24 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; +import static java.util.stream.Collectors.*; + +/// Proof: collectors-flatmapping +/// Source: content/streams/collectors-flatmapping.yaml +record Emp(String dept, List tags) {} + +void main() { + var employees = List.of( + new Emp("eng", List.of("java", "cloud")), + new Emp("eng", List.of("java", "ai")) + ); + var tagsByDept = employees.stream() + .collect(groupingBy( + Emp::dept, + flatMapping( + e -> e.tags().stream(), + toSet() + ) + )); +} diff --git a/proof/streams/OptionalIfpresentorelse.java b/proof/streams/OptionalIfpresentorelse.java new file mode 100755 index 0000000..2a5484e --- /dev/null +++ b/proof/streams/OptionalIfpresentorelse.java @@ -0,0 +1,22 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: optional-ifpresentorelse +/// Source: content/streams/optional-ifpresentorelse.yaml +record User(String name) {} + +Optional findUser(String id) { + return id.equals("known") ? Optional.of(new User("Alice")) : Optional.empty(); +} + +void greet(User u) { System.out.println("Hello " + u.name()); } +void handleMissing() { System.out.println("not found"); } + +void main() { + String id = "known"; + findUser(id).ifPresentOrElse( + this::greet, + this::handleMissing + ); +} diff --git a/proof/streams/OptionalOr.java b/proof/streams/OptionalOr.java new file mode 100755 index 0000000..6f722c3 --- /dev/null +++ b/proof/streams/OptionalOr.java @@ -0,0 +1,17 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; + +/// Proof: optional-or +/// Source: content/streams/optional-or.yaml +record Config(String value) {} + +Optional primary() { return Optional.empty(); } +Optional secondary() { return Optional.empty(); } +Optional defaults() { return Optional.of(new Config("default")); } + +void main() { + Optional cfg = primary() + .or(this::secondary) + .or(this::defaults); +} diff --git a/proof/streams/PredicateNot.java b/proof/streams/PredicateNot.java new file mode 100755 index 0000000..3ee7ad6 --- /dev/null +++ b/proof/streams/PredicateNot.java @@ -0,0 +1,13 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.function.*; + +/// Proof: predicate-not +/// Source: content/streams/predicate-not.yaml +void main() { + List list = List.of("hello", "", "world", " "); + List nonEmpty = list.stream() + .filter(Predicate.not(String::isBlank)) + .toList(); +} diff --git a/proof/streams/StreamGatherers.java b/proof/streams/StreamGatherers.java new file mode 100755 index 0000000..5874318 --- /dev/null +++ b/proof/streams/StreamGatherers.java @@ -0,0 +1,15 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; + +/// Proof: stream-gatherers +/// Source: content/streams/stream-gatherers.yaml +void main() { + Stream stream = Stream.of(1, 2, 3, 4, 5); + var windows = stream + .gather( + Gatherers.windowSliding(3) + ) + .toList(); +} diff --git a/proof/streams/StreamIteratePredicate.java b/proof/streams/StreamIteratePredicate.java new file mode 100755 index 0000000..c445514 --- /dev/null +++ b/proof/streams/StreamIteratePredicate.java @@ -0,0 +1,14 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.stream.*; + +/// Proof: stream-iterate-predicate +/// Source: content/streams/stream-iterate-predicate.yaml +void main() { + Stream.iterate( + 1, + n -> n < 1000, + n -> n * 2 + ).forEach(System.out::println); + // stops when n >= 1000 +} diff --git a/proof/streams/StreamMapmulti.java b/proof/streams/StreamMapmulti.java new file mode 100755 index 0000000..724e3ea --- /dev/null +++ b/proof/streams/StreamMapmulti.java @@ -0,0 +1,24 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; + +/// Proof: stream-mapmulti +/// Source: content/streams/stream-mapmulti.yaml +record Order(String id, List items) {} +record OrderItem(String orderId, String item) {} + +void main() { + var orders = List.of( + new Order("o1", List.of("a", "b")), + new Order("o2", List.of("c")) + ); + Stream stream = orders.stream(); + stream.mapMulti( + (order, downstream) -> { + for (var item : order.items()) + downstream.accept( + new OrderItem(order.id(), item)); + } + ).toList(); +} diff --git a/proof/streams/StreamOfNullable.java b/proof/streams/StreamOfNullable.java new file mode 100755 index 0000000..b47a2ef --- /dev/null +++ b/proof/streams/StreamOfNullable.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.stream.*; + +/// Proof: stream-of-nullable +/// Source: content/streams/stream-of-nullable.yaml +void main() { + String val = null; + Stream s = + Stream.ofNullable(val); +} diff --git a/proof/streams/StreamTakewhileDropwhile.java b/proof/streams/StreamTakewhileDropwhile.java new file mode 100755 index 0000000..e9f9ee2 --- /dev/null +++ b/proof/streams/StreamTakewhileDropwhile.java @@ -0,0 +1,14 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.stream.*; + +/// Proof: stream-takewhile-dropwhile +/// Source: content/streams/stream-takewhile-dropwhile.yaml +void main() { + List sorted = List.of(1, 5, 50, 100, 150, 200); + var result = sorted.stream() + .takeWhile(n -> n < 100) + .toList(); + // or: .dropWhile(n -> n < 10) +} diff --git a/proof/streams/StreamTolist.java b/proof/streams/StreamTolist.java new file mode 100755 index 0000000..0554993 --- /dev/null +++ b/proof/streams/StreamTolist.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.stream.*; + +/// Proof: stream-tolist +/// Source: content/streams/stream-tolist.yaml +void main() { + Stream stream = Stream.of("hello", "hi", "world"); + List result = stream + .filter(s -> s.length() > 3) + .toList(); +} diff --git a/proof/streams/VirtualThreadExecutor.java b/proof/streams/VirtualThreadExecutor.java new file mode 100755 index 0000000..2bd7d86 --- /dev/null +++ b/proof/streams/VirtualThreadExecutor.java @@ -0,0 +1,18 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.util.*; +import java.util.concurrent.*; + +/// Proof: virtual-thread-executor +/// Source: content/streams/virtual-thread-executor.yaml +interface Task { String run() throws Exception; } + +void main() throws Exception { + List tasks = List.of(() -> "result1", () -> "result2"); + try (var exec = Executors + .newVirtualThreadPerTaskExecutor()) { + var futures = tasks.stream() + .map(t -> exec.submit(t::run)) + .toList(); + } +} diff --git a/proof/strings/StringCharsStream.java b/proof/strings/StringCharsStream.java new file mode 100755 index 0000000..5e410ce --- /dev/null +++ b/proof/strings/StringCharsStream.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-chars-stream +/// Source: content/strings/string-chars-stream.yaml +void process(char c) {} + +void main() { + String str = "abc123"; + str.chars() + .filter(Character::isDigit) + .forEach(c -> process((char) c)); +} diff --git a/proof/strings/StringFormatted.java b/proof/strings/StringFormatted.java new file mode 100755 index 0000000..3a1bbbe --- /dev/null +++ b/proof/strings/StringFormatted.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-formatted +/// Source: content/strings/string-formatted.yaml +void main() { + String name = "Alice"; + int age = 30; + String msg = + "Hello %s, you are %d" + .formatted(name, age); +} diff --git a/proof/strings/StringIndentTransform.java b/proof/strings/StringIndentTransform.java new file mode 100755 index 0000000..9710f2d --- /dev/null +++ b/proof/strings/StringIndentTransform.java @@ -0,0 +1,12 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-indent-transform +/// Source: content/strings/string-indent-transform.yaml +void main() { + String text = "hello world"; + String indented = text.indent(4); + + String result = text + .transform(String::strip) + .transform(s -> s.replace(" ", "-")); +} diff --git a/proof/strings/StringIsblank.java b/proof/strings/StringIsblank.java new file mode 100755 index 0000000..3956702 --- /dev/null +++ b/proof/strings/StringIsblank.java @@ -0,0 +1,9 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-isblank +/// Source: content/strings/string-isblank.yaml +void main() { + String str = " \t "; + boolean blank = str.isBlank(); + // handles Unicode whitespace too +} diff --git a/proof/strings/StringLines.java b/proof/strings/StringLines.java new file mode 100755 index 0000000..3a078b0 --- /dev/null +++ b/proof/strings/StringLines.java @@ -0,0 +1,8 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-lines +/// Source: content/strings/string-lines.yaml +void main() { + String text = "one\ntwo\nthree"; + text.lines().forEach(System.out::println); +} diff --git a/proof/strings/StringRepeat.java b/proof/strings/StringRepeat.java new file mode 100755 index 0000000..579005b --- /dev/null +++ b/proof/strings/StringRepeat.java @@ -0,0 +1,8 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-repeat +/// Source: content/strings/string-repeat.yaml +void main() { + String result = "abc".repeat(3); + // "abcabcabc" +} diff --git a/proof/strings/StringStrip.java b/proof/strings/StringStrip.java new file mode 100755 index 0000000..7b1eb9a --- /dev/null +++ b/proof/strings/StringStrip.java @@ -0,0 +1,11 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +/// Proof: string-strip +/// Source: content/strings/string-strip.yaml +void main() { + String str = " hello world "; + // strip() removes all Unicode whitespace + String clean = str.strip(); + String left = str.stripLeading(); + String right = str.stripTrailing(); +} diff --git a/proof/tooling/BuiltInHttpServer.java b/proof/tooling/BuiltInHttpServer.java new file mode 100755 index 0000000..0b62480 --- /dev/null +++ b/proof/tooling/BuiltInHttpServer.java @@ -0,0 +1,16 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +import java.net.*; +import java.nio.file.*; +import com.sun.net.httpserver.*; + +/// Proof: built-in-http-server +/// Source: content/tooling/built-in-http-server.yaml +void main() throws Exception { + // Or use the API (JDK 18+) + var server = SimpleFileServer.createFileServer( + new InetSocketAddress(0), + Path.of(System.getProperty("java.io.tmpdir")).toAbsolutePath(), + SimpleFileServer.OutputLevel.VERBOSE); + // server.start(); // not started in proof +} diff --git a/proof/tooling/Junit6WithJspecify.java b/proof/tooling/Junit6WithJspecify.java new file mode 100755 index 0000000..cccfb1d --- /dev/null +++ b/proof/tooling/Junit6WithJspecify.java @@ -0,0 +1,44 @@ +///usr/bin/env jbang "$0" "$@" ; exit $? +//JAVA 25+ +//DEPS org.junit.jupiter:junit-jupiter-api:6.0.3 +//DEPS org.jspecify:jspecify:1.0.0 + +import org.junit.jupiter.api.Test; +import org.jspecify.annotations.NullMarked; +import org.jspecify.annotations.Nullable; +import static org.junit.jupiter.api.Assertions.*; + +/// Proof: junit6-with-jspecify +/// Source: content/tooling/junit6-with-jspecify.yaml +record User(String name) {} + +interface UserService { + @Nullable User findById(String id); +} + +UserService service = id -> id.equals("u1") ? new User("Alice") : null; + +@NullMarked // all refs non-null unless @Nullable +class UserServiceTest { + + // JUnit 6 API is @NullMarked: + // assertNull(@Nullable Object actual) + // assertEquals(@Nullable Object, @Nullable Object) + // fail(@Nullable String message) + + @Test + void findUser_found() { + // IDE warns: findById returns @Nullable User + @Nullable User result = service.findById("u1"); + assertNotNull(result); // narrows type to non-null + assertEquals("Alice", result.name()); // safe + } + + @Test + void findUser_notFound() { + @Nullable User result = service.findById("missing"); + assertNull(result); // IDE confirms null expectation + } +} + +void main() {}