diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 8e377f0..f05bb75 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -9,6 +9,7 @@
diff --git a/basic-concurrency/build.gradle b/basic-concurrency/build.gradle
new file mode 100644
index 0000000..e35abb8
--- /dev/null
+++ b/basic-concurrency/build.gradle
@@ -0,0 +1,23 @@
+plugins {
+ id 'java'
+}
+
+getTasks().withType(JavaCompile.class).configureEach {
+ getOptions().getRelease().set(9)
+}
+
+repositories {
+ mavenCentral()
+}
+
+dependencies {
+ implementation libs.contracts
+ implementation libs.concurrency
+
+ testImplementation libs.contracts.test
+ testImplementation libs.concurrency.test
+}
+
+test {
+ useJUnitPlatform()
+}
\ No newline at end of file
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Command.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Command.java
new file mode 100644
index 0000000..ab4bcaa
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Command.java
@@ -0,0 +1,12 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+@FunctionalInterface
+public interface Command {
+
+ String execute();
+
+ default boolean foreground() {
+ return false;
+ }
+
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Constants.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Constants.java
new file mode 100644
index 0000000..f276258
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Constants.java
@@ -0,0 +1,58 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+import io.github.jonloucks.concurrency.api.Waitable;
+import io.github.jonloucks.contracts.api.Contract;
+
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Contracts can be defined anywhere, placing them here to demonstrate package private access
+ */
+final class Constants {
+
+ /**
+ * Constant string, but open for uses cases like localization.
+ */
+ static final Contract PROGRAM_NAME = Contract.create("Program name contract");
+
+ /**
+ * The main program implementation, not everything has to be in the class with main()
+ */
+ static final Contract PROGRAM = Contract.create("Program contract");
+
+ /**
+ * Example of a configuration setting that is a singleton with lazy evaluation of value
+ */
+ static final Contract RUNNER_THREAD_COUNT = Contract.create("Number of worker threads contract");
+
+ /**
+ * Example of a shared executor with auto resource management
+ */
+ static final Contract RUNNER = Contract.create("Dispatcher contract");
+
+ /**
+ * Set to true when program is quitting
+ */
+ static final Contract> IS_QUITTING = Contract.create("Quitting contract");
+
+ /**
+ * The health the program
+ */
+ static final Contract> HEALTH = Contract.create("Health contract");
+
+ /**
+ * Command Output
+ */
+ static final Contract> OUTPUT = Contract.create("Command output contract");
+
+ /**
+ * Command Input
+ */
+ static final Contract> INPUT = Contract.create("Command input contract");
+
+ /**
+ * Command Output
+ */
+ static final Contract> ERROR = Contract.create("Command error contract");
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Dispatcher.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Dispatcher.java
new file mode 100644
index 0000000..0ab85f9
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Dispatcher.java
@@ -0,0 +1,6 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+public interface Dispatcher {
+
+ void dispatch(Command command);
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/DispatcherImpl.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/DispatcherImpl.java
new file mode 100644
index 0000000..38787ec
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/DispatcherImpl.java
@@ -0,0 +1,69 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+import io.github.jonloucks.concurrency.api.OnCompletion;
+import io.github.jonloucks.concurrency.api.Waitable;
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.AutoOpen;
+
+import java.util.concurrent.*;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.function.Consumer;
+
+import static io.github.jonloucks.concurrency.api.GlobalConcurrency.*;
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.concurrency.basic.Constants.*;
+import static java.util.Optional.ofNullable;
+
+final class DispatcherImpl implements Dispatcher, AutoOpen {
+ @Override
+ public void dispatch(Command command) {
+ incrementRunning();
+ final OnCompletion onCompletion = c -> {
+ c.getValue().ifPresent(output);
+ c.getThrown().ifPresent(e ->error.accept(e.getMessage()));
+ decrementRunning();
+ };
+ if (!command.foreground() && ofNullable(delegate).isPresent()) {
+ completeLater(onCompletion, on -> delegate.execute(() -> completeNow(on, command::execute)));
+ } else {
+ completeNow(onCompletion, command::execute);
+ }
+ }
+
+ @Override
+ public AutoClose open() {
+ delegate = Executors.newFixedThreadPool(claimContract(RUNNER_THREAD_COUNT));
+ return this::privateClose; // only open caller can close
+ }
+
+ private void privateClose() {
+ ofNullable(delegate).ifPresent(executor -> {
+ delegate = null;
+ executor.shutdown();
+ try {
+ if (executor.awaitTermination(5, TimeUnit.MINUTES)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException ignored) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ }
+
+ private void incrementRunning() {
+ runningCommandCount.incrementAndGet();
+ health.accept("busy");
+ }
+
+ private void decrementRunning() {
+ if (runningCommandCount.decrementAndGet() == 0) {
+ health.accept("idle");
+ }
+ }
+
+ private ExecutorService delegate;
+ private final AtomicInteger runningCommandCount = new AtomicInteger();
+ private final Consumer output = claimContract(OUTPUT);
+ private final Consumer error = claimContract(ERROR);
+ private final Waitable health = claimContract(HEALTH);
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/HelpCommand.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/HelpCommand.java
new file mode 100644
index 0000000..1fc5c48
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/HelpCommand.java
@@ -0,0 +1,17 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+final class HelpCommand implements Command {
+ @Override
+ public String execute() {
+ return "This is an example...";
+ }
+
+ @Override
+ public boolean foreground() {
+ return true;
+ }
+
+ HelpCommand() {
+
+ }
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Main.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Main.java
new file mode 100644
index 0000000..38177c3
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Main.java
@@ -0,0 +1,95 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.BindStrategy;
+import io.github.jonloucks.contracts.api.Repository;
+
+import java.util.Scanner;
+
+import static io.github.jonloucks.concurrency.api.GlobalConcurrency.createWaitable;
+import static io.github.jonloucks.contracts.api.BindStrategy.IF_NOT_BOUND;
+import static io.github.jonloucks.contracts.api.GlobalContracts.*;
+import static io.github.jonloucks.examples.concurrency.basic.Constants.*;
+import static java.lang.Boolean.TRUE;
+
+@SuppressWarnings("unused")
+public final class Main {
+
+ /**
+ * Main entry point.
+ * Note. Entry points are where final decisions on dependency inversions are made.
+ * Not all decisions must be made in an entry point, but in this example it is exposed
+ * for visibility
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ final Repository repository = createMyRepository();
+ try (AutoClose closeRepository = repository.open();
+ AutoClose closeHealthNotify = openHealthNotify()) {
+
+ final Program program = claimContract(PROGRAM);
+
+ waitForQuit();
+
+ waitForIdle();
+ } // all repository resources will be released when this try block exits
+ }
+
+ private static void waitForQuit() {
+ claimContract(IS_QUITTING).getWhen(TRUE::equals);
+ }
+
+ private static void waitForIdle() {
+ claimContract(HEALTH).getWhen("idle"::equals);
+ }
+
+ private static AutoClose openHealthNotify() {
+ return claimContract(HEALTH).notifyIf("idle"::equals, health -> {
+ System.out.println( "All commands have completed.");
+ });
+ }
+
+ /**
+ * For complex projects with many modules and entry points there a many
+ * ways to extend and manage dependency inversions without
+ * have them all defined here.
+ * For example:
+ * each module could have its own Repository.
+ * MyModule.createRepository(contracts)
+ * each module could have a static method to install contracts to a given repository
+ * MyModule.install(repository)
+ * each module could use the ServiceLoader mechanism
+ * I am sure there are more like SpringBoot
+ */
+ private static Repository createMyRepository() {
+ final Repository repository = claimContract(Repository.FACTORY).get();
+
+ final BindStrategy strategy = IF_NOT_BOUND;
+
+ // Constant string, but could be changed to a localized value without changing uses
+ repository.keep(PROGRAM_NAME, () -> "Concurrency Example");
+
+ // lifeCycle will create a singleton and detect AutoOpen implementations
+ repository.keep(PROGRAM, lifeCycle(ProgramImpl::new));
+
+ repository.keep(OUTPUT, singleton(() -> text -> System.out.println(text)), strategy);
+
+ repository.keep(ERROR, singleton(() -> text -> System.err.println(text)), strategy);
+
+ repository.keep(INPUT, singleton(() -> new Scanner(System.in)::nextLine), strategy);
+
+ // lazy evaluated singleton
+ repository.keep(RUNNER_THREAD_COUNT, singleton(() -> Runtime.getRuntime().availableProcessors() * 8));
+
+ // lifeCycle will create a singleton and detect AutoOpen implementations
+ repository.keep(RUNNER, lifeCycle(DispatcherImpl::new));
+
+ // set to true when program is quiting
+ repository.keep(IS_QUITTING, singleton(() -> createWaitable(false)));
+
+ // program health
+ repository.keep(HEALTH, singleton(() -> createWaitable("ready")));
+
+ return repository;
+ }
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Program.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Program.java
new file mode 100644
index 0000000..a99dbec
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/Program.java
@@ -0,0 +1,4 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+interface Program {
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/ProgramImpl.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/ProgramImpl.java
new file mode 100644
index 0000000..37d97ca
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/ProgramImpl.java
@@ -0,0 +1,77 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.AutoOpen;
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.concurrency.basic.Constants.*;
+import static java.util.Optional.ofNullable;
+
+final class ProgramImpl implements Program, AutoOpen {
+
+ @Override
+ public AutoClose open() {
+ serviceStart = Instant.now();
+ dispatcher = claimContract(Constants.RUNNER);
+ output.accept("Welcome to " + claimContract(PROGRAM_NAME));
+ new Thread(this::commandLoop).start();
+ return this::privateClose;
+ }
+
+ ProgramImpl() {
+ }
+
+ void commandLoop() {
+ while (!isQuitting()) {
+ promptForCommand().ifPresent(dispatcher::dispatch);
+ }
+ }
+
+ private Duration getUptime() {
+ return Duration.between(serviceStart, ofNullable(serviceEnd).orElseGet(Instant::now));
+ }
+
+ private boolean isQuitting() {
+ return claimContract(IS_QUITTING).get();
+ }
+
+ private Optional promptForCommand() {
+ try {
+ output.accept("Enter command: ");
+ return Optional.of(parseCommand(input.get())); // Read the entire line until a newline character
+ } catch (Exception thrown) {
+ claimContract(IS_QUITTING).accept(true);
+ return Optional.empty();
+ }
+ }
+
+ private Command parseCommand(String commandLine) {
+ switch (ofNullable(commandLine).orElse("").toLowerCase()) {
+ case "":
+ case "help":
+ case "?":
+ return new HelpCommand();
+ case "quit":
+ return new QuitCommand();
+ default:
+ return () -> "Unrecognized command: " + commandLine;
+ }
+ }
+
+ private void privateClose() {
+ serviceEnd = Instant.now();
+ output.accept("Service Uptime: " + getUptime());
+ }
+
+ private final Supplier input = claimContract(INPUT);
+ private final Consumer output = claimContract(OUTPUT);
+ private Instant serviceStart;
+ private Instant serviceEnd;
+ private Dispatcher dispatcher;
+}
diff --git a/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/QuitCommand.java b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/QuitCommand.java
new file mode 100644
index 0000000..c663f65
--- /dev/null
+++ b/basic-concurrency/src/main/java/io/github/jonloucks/examples/concurrency/basic/QuitCommand.java
@@ -0,0 +1,21 @@
+package io.github.jonloucks.examples.concurrency.basic;
+
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.concurrency.basic.Constants.IS_QUITTING;
+
+final class QuitCommand implements Command {
+ @Override
+ public String execute() {
+ claimContract(IS_QUITTING).accept(true);
+ return "Quit initiated";
+ }
+
+ @Override
+ public boolean foreground() {
+ return true;
+ }
+
+ QuitCommand() {
+
+ }
+}
diff --git a/basic-concurrency/src/main/java/module-info.java b/basic-concurrency/src/main/java/module-info.java
new file mode 100644
index 0000000..97bd0ba
--- /dev/null
+++ b/basic-concurrency/src/main/java/module-info.java
@@ -0,0 +1,6 @@
+module io.github.jonloucks.examples.concurrency.basic {
+ requires transitive io.github.jonloucks.contracts;
+ requires transitive io.github.jonloucks.concurrency;
+
+ exports io.github.jonloucks.examples.concurrency.basic;
+}
\ No newline at end of file
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/Constants.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/Constants.java
deleted file mode 100644
index c4face6..0000000
--- a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/Constants.java
+++ /dev/null
@@ -1,31 +0,0 @@
-package io.github.jonloucks.examples.basic.contracts;
-
-import io.github.jonloucks.contracts.api.Contract;
-
-import java.util.concurrent.Executor;
-
-/**
- * Contracts can be defined anywhere, placing them here to demonstrate package private access
- */
-final class Constants {
-
- /**
- * Constant string, but open for uses cases like localization.
- */
- static final Contract PROGRAM_NAME_CONTRACT = Contract.create("Program name");
-
- /**
- * The main program implementation, not everything has to be in the class with main()
- */
- static final Contract PROGRAM_CONTRACT = Contract.create("Program contract");
-
- /**
- * Example of a configuration setting that is a singleton with lazy evaluation of value
- */
- static final Contract WORKER_THREAD_COUNT = Contract.create("Number of worker threads contract");
-
- /**
- * Example of a shared executor with auto resource management
- */
- static final Contract EXECUTOR_CONTRACT = Contract.create("Executor contract");
-}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/Main.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/Main.java
deleted file mode 100644
index c09a158..0000000
--- a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/Main.java
+++ /dev/null
@@ -1,44 +0,0 @@
-package io.github.jonloucks.examples.basic.contracts;
-
-import io.github.jonloucks.contracts.api.AutoClose;
-import io.github.jonloucks.contracts.api.Repository;
-
-import static io.github.jonloucks.contracts.api.GlobalContracts.*;
-import static io.github.jonloucks.examples.basic.contracts.Constants.*;
-
-public final class Main {
-
- /**
- * Main entry point
- * @param args the command line arguments
- */
- public static void main(String[] args) {
- final Repository repository = createMyRepository();
- try (AutoClose closeRepository = repository.open()) {
- System.out.println("Welcome to " + claimContract(PROGRAM_NAME_CONTRACT));
- final MyProgram myProgram = claimContract(PROGRAM_CONTRACT);
-
- myProgram.runCommand(args);
-
- System.out.println("Service Uptime: " + myProgram.getUptime());
- } // all resources will be released when this try block exits
- }
-
- private static Repository createMyRepository() {
- final Repository repository = claimContract(Repository.FACTORY).get();
-
- // Constant string, but could be changed to a localized value without changing uses
- repository.keep(PROGRAM_NAME_CONTRACT, () -> "Contracts Example");
-
- // lifeCycle will create a singleton and detect AutoOpen implementations
- repository.keep(PROGRAM_CONTRACT, lifeCycle(MyProgramImpl::new));
-
- // lazy evaluated singleton
- repository.keep(WORKER_THREAD_COUNT, singleton(() -> Runtime.getRuntime().availableProcessors() * 8));
-
- // lifeCycle will create a singleton and detect AutoOpen implementations
- repository.keep(EXECUTOR_CONTRACT, lifeCycle(MyExecutor::new));
-
- return repository;
- }
-}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyExecutor.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyExecutor.java
deleted file mode 100644
index c491256..0000000
--- a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyExecutor.java
+++ /dev/null
@@ -1,45 +0,0 @@
-package io.github.jonloucks.examples.basic.contracts;
-
-import io.github.jonloucks.contracts.api.AutoClose;
-import io.github.jonloucks.contracts.api.AutoOpen;
-
-import java.util.concurrent.Executor;
-import java.util.concurrent.ExecutorService;
-import java.util.concurrent.Executors;
-import java.util.concurrent.TimeUnit;
-
-import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
-import static java.util.Optional.ofNullable;
-
-final class MyExecutor implements Executor, AutoOpen {
- @Override
- public void execute(Runnable command) {
- if (ofNullable(delegate).isPresent()) {
- delegate.execute(command);
- } else {
- command.run();
- }
- }
-
- @Override
- public AutoClose open() {
- delegate = Executors.newFixedThreadPool(claimContract(Constants.WORKER_THREAD_COUNT));
- return this::myClose;
- }
-
- private void myClose() {
- ofNullable(delegate).ifPresent(executor -> {
- delegate = null;
- executor.shutdown();
- try {
- if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
- executor.shutdownNow();
- }
- } catch (InterruptedException ignored) {
- Thread.currentThread().interrupt();
- }
- });
- }
-
- private ExecutorService delegate;
-}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyProgram.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyProgram.java
deleted file mode 100644
index f3ed9d5..0000000
--- a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyProgram.java
+++ /dev/null
@@ -1,9 +0,0 @@
-package io.github.jonloucks.examples.basic.contracts;
-
-import java.time.Duration;
-
-interface MyProgram {
- Duration getUptime();
-
- void runCommand(String[] args);
-}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyProgramImpl.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyProgramImpl.java
deleted file mode 100644
index 93adcbb..0000000
--- a/basic-contracts/src/main/java/io/github/jonloucks/examples/basic/contracts/MyProgramImpl.java
+++ /dev/null
@@ -1,47 +0,0 @@
-package io.github.jonloucks.examples.basic.contracts;
-
-import io.github.jonloucks.contracts.api.AutoClose;
-import io.github.jonloucks.contracts.api.AutoOpen;
-
-import java.time.Duration;
-import java.time.Instant;
-import java.util.Arrays;
-import java.util.concurrent.Executor;
-
-import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
-import static java.util.Optional.ofNullable;
-
-final class MyProgramImpl implements MyProgram, AutoOpen {
-
- @Override
- public AutoClose open() {
- serviceStart = Instant.now();
- executor = claimContract(Constants.EXECUTOR_CONTRACT);
- return this::close;
- }
-
- @Override
- public Duration getUptime() {
- return Duration.between(serviceStart, ofNullable(serviceEnd).orElseGet(Instant::now));
- }
-
- @Override
- public void runCommand(String[] args) {
- if (args.length == 0) {
- executor.execute(() -> System.out.println("Command not specified."));
- } else {
- executor.execute(() -> System.out.println("Unrecognized command: " + Arrays.toString(args)));
- }
- }
-
- MyProgramImpl() {
- }
-
- private void close() {
- serviceEnd = Instant.now();
- }
-
- private Instant serviceStart;
- private Instant serviceEnd;
- private Executor executor;
-}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Command.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Command.java
new file mode 100644
index 0000000..56e89c5
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Command.java
@@ -0,0 +1,12 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+@FunctionalInterface
+public interface Command {
+
+ String execute();
+
+ default boolean foreground() {
+ return false;
+ }
+
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Constants.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Constants.java
new file mode 100644
index 0000000..9295785
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Constants.java
@@ -0,0 +1,60 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+import io.github.jonloucks.contracts.api.Contract;
+
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+/**
+ * Contracts can be defined anywhere, placing them here to demonstrate package private access
+ */
+final class Constants {
+
+ /**
+ * Constant string, but open for uses cases like localization.
+ */
+ static final Contract PROGRAM_NAME = Contract.create("Program name contract");
+
+ /**
+ * The main program implementation, not everything has to be in the class with main()
+ */
+ static final Contract PROGRAM = Contract.create("Program contract");
+
+ /**
+ * Example of a configuration setting that is a singleton with lazy evaluation of value
+ */
+ static final Contract RUNNER_THREAD_COUNT = Contract.create("Number of worker threads contract");
+
+ /**
+ * Example of a shared executor with auto resource management
+ */
+ static final Contract DISPATCHER = Contract.create("Dispatcher contract");
+
+ /**
+ * Set to true when program is quitting
+ */
+ static final Contract IS_QUITTING = Contract.create("Quitting contract");
+
+ /**
+ * The health the program
+ */
+ static final Contract> HEALTH = Contract.create("Health contract");
+
+ /**
+ * Command Output
+ */
+ static final Contract> OUTPUT = Contract.create("Command output contract");
+
+ /**
+ * Command Input
+ */
+ static final Contract> INPUT = Contract.create("Command input contract");
+
+ /**
+ * Command Output
+ */
+ static final Contract> ERROR = Contract.create("Command error contract");
+
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Dispatcher.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Dispatcher.java
new file mode 100644
index 0000000..042f966
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Dispatcher.java
@@ -0,0 +1,6 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+public interface Dispatcher {
+
+ void dispatch(Command command);
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/DispatcherImpl.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/DispatcherImpl.java
new file mode 100644
index 0000000..26627eb
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/DispatcherImpl.java
@@ -0,0 +1,76 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.AutoOpen;
+
+import java.util.concurrent.ExecutorService;
+import java.util.concurrent.Executors;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Consumer;
+
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.contracts.basic.Constants.*;
+import static java.util.Optional.ofNullable;
+
+final class DispatcherImpl implements Dispatcher, AutoOpen {
+
+ @Override
+ public void dispatch(Command command) {
+ incrementRunning();
+
+ final Runnable job = () -> {
+ try {
+ output.accept(command.execute());
+ } catch (Throwable thrown) {
+ error.accept(thrown.getMessage());
+ } finally {
+ decrementRunning();
+ }
+ };
+
+ if (!command.foreground() && ofNullable(delegate).isPresent()) {
+ delegate.execute(job);
+ } else {
+ job.run();
+ }
+ }
+
+ @Override
+ public AutoClose open() {
+ delegate = Executors.newFixedThreadPool(claimContract(RUNNER_THREAD_COUNT));
+ return this::privateClose; // only open caller can close
+ }
+
+ private void privateClose() {
+ ofNullable(delegate).ifPresent(executor -> {
+ delegate = null;
+ executor.shutdown();
+ try {
+ if (!executor.awaitTermination(1, TimeUnit.MINUTES)) {
+ executor.shutdownNow();
+ }
+ } catch (InterruptedException ignored) {
+ Thread.currentThread().interrupt();
+ }
+ });
+ }
+
+ private void incrementRunning() {
+ runningCommandCount.incrementAndGet();
+ health.set("busy");
+ }
+
+ private void decrementRunning() {
+ if (runningCommandCount.decrementAndGet() == 0) {
+ health.set("idle");
+ }
+ }
+
+ private ExecutorService delegate;
+ private final AtomicInteger runningCommandCount = new AtomicInteger();
+ private final Consumer error = claimContract(ERROR);
+ private final Consumer output = claimContract(OUTPUT);
+ private final AtomicReference health = claimContract(HEALTH);
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/HelpCommand.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/HelpCommand.java
new file mode 100644
index 0000000..224ab08
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/HelpCommand.java
@@ -0,0 +1,17 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+final class HelpCommand implements Command {
+ @Override
+ public String execute() {
+ return "This is an example...";
+ }
+
+ @Override
+ public boolean foreground() {
+ return true;
+ }
+
+ HelpCommand() {
+
+ }
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Main.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Main.java
new file mode 100644
index 0000000..3f20fbf
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Main.java
@@ -0,0 +1,86 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.BindStrategy;
+import io.github.jonloucks.contracts.api.Repository;
+
+import java.util.Scanner;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.atomic.AtomicReference;
+
+import static io.github.jonloucks.contracts.api.BindStrategy.IF_NOT_BOUND;
+import static io.github.jonloucks.contracts.api.GlobalContracts.*;
+import static io.github.jonloucks.examples.contracts.basic.Constants.*;
+
+@SuppressWarnings("unused")
+public final class Main {
+
+ /**
+ * Main entry point.
+ * Note. Entry points are where final decisions on dependency inversions are made.
+ * Not all decisions must be made in an entry point, but in this example it is exposed
+ * for visibility
+ * @param args the command line arguments
+ */
+ public static void main(String[] args) {
+ final Repository repository = createMyRepository();
+ try (AutoClose closeRepository = repository.open()) {
+
+ final Program program = claimContract(PROGRAM);
+
+ waitForQuit();
+ } // all repository resources will be released when this try block exits
+ }
+
+ private static void waitForQuit() {
+ try {
+ claimContract(IS_QUITTING).await();
+ } catch (InterruptedException ignored) {
+ throw new RuntimeException("Waiting for quit");
+ }
+ }
+
+ /**
+ * For complex projects with many modules and entry points there a many
+ * ways to extend and manage dependency inversions without
+ * have them all defined here.
+ * For example:
+ * each module could have its own Repository.
+ * MyModule.createRepository(contracts)
+ * each module could have a static method to install contracts to a given repository
+ * MyModule.install(repository)
+ * each module could use the ServiceLoader mechanism
+ * I am sure there are more like SpringBoot
+ */
+ private static Repository createMyRepository() {
+ final Repository repository = claimContract(Repository.FACTORY).get();
+
+ final BindStrategy strategy = IF_NOT_BOUND;
+
+ // Constant string, but could be changed to a localized value without changing uses
+ repository.keep(PROGRAM_NAME, () -> "Concurrency Example", strategy);
+
+ // lifeCycle will create a singleton and detect AutoOpen implementations
+ repository.keep(PROGRAM, lifeCycle(ProgramImpl::new), strategy);
+
+ repository.keep(OUTPUT, singleton(() -> text -> System.out.println(text)), strategy);
+
+ repository.keep(ERROR, singleton(() -> text -> System.err.println(text)), strategy);
+
+ repository.keep(INPUT, singleton(() -> new Scanner(System.in)::nextLine), strategy);
+
+ // lazy evaluated singleton
+ repository.keep(RUNNER_THREAD_COUNT, singleton(() -> Runtime.getRuntime().availableProcessors() * 8), strategy);
+
+ // lifeCycle will create a singleton and detect AutoOpen implementations
+ repository.keep(DISPATCHER, lifeCycle(DispatcherImpl::new), strategy);
+
+ // set to true when program is quiting
+ repository.keep(IS_QUITTING, singleton(() -> new CountDownLatch(1)), strategy);
+
+ // program health
+ repository.keep(HEALTH, singleton(() -> new AtomicReference<>("ready")), strategy);
+
+ return repository;
+ }
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Program.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Program.java
new file mode 100644
index 0000000..2be3712
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/Program.java
@@ -0,0 +1,5 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+interface Program {
+
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/ProgramImpl.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/ProgramImpl.java
new file mode 100644
index 0000000..85eeb9c
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/ProgramImpl.java
@@ -0,0 +1,83 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+import io.github.jonloucks.contracts.api.AutoClose;
+import io.github.jonloucks.contracts.api.AutoOpen;
+
+
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Optional;
+import java.util.concurrent.TimeUnit;
+import java.util.function.Consumer;
+import java.util.function.Supplier;
+
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.contracts.basic.Constants.*;
+import static java.util.Optional.ofNullable;
+
+final class ProgramImpl implements Program, AutoOpen {
+
+ @Override
+ public AutoClose open() {
+ serviceStart = Instant.now();
+ dispatcher = claimContract(DISPATCHER);
+ output.accept("Welcome to " + claimContract(PROGRAM_NAME));
+ new Thread(this::commandLoop).start();
+ return this::privateClose;
+ }
+
+ ProgramImpl() {
+ }
+
+ private void commandLoop() {
+ while (!isQuitting()) {
+ promptForCommand().ifPresent(dispatcher::dispatch);
+ }
+ }
+
+ private Duration getUptime() {
+ return Duration.between(serviceStart, ofNullable(serviceEnd).orElseGet(Instant::now));
+ }
+
+ private boolean isQuitting() {
+ try {
+ return claimContract(IS_QUITTING).await(0, TimeUnit.MINUTES);
+ } catch (InterruptedException e) {
+ return true;
+ }
+ }
+
+ private Optional promptForCommand() {
+ try {
+ output.accept("Enter command: ");
+ return Optional.of(parseCommand(input.get())); // Read the entire line until a newline character
+ } catch (Exception thrown) {
+ claimContract(IS_QUITTING).countDown();
+ return Optional.empty();
+ }
+ }
+
+ private Command parseCommand(String commandLine) {
+ switch (ofNullable(commandLine).orElse("").toLowerCase()) {
+ case "":
+ case "help":
+ case "?":
+ return new HelpCommand();
+ case "quit":
+ return new QuitCommand();
+ default:
+ return () -> "Unrecognized command: " + commandLine;
+ }
+ }
+
+ private void privateClose() {
+ serviceEnd = Instant.now();
+ output.accept("Service Uptime: " + getUptime());
+ }
+
+ private final Supplier input = claimContract(INPUT);
+ private final Consumer output = claimContract(OUTPUT);
+ private Instant serviceStart;
+ private Instant serviceEnd;
+ private Dispatcher dispatcher;
+}
diff --git a/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/QuitCommand.java b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/QuitCommand.java
new file mode 100644
index 0000000..1361343
--- /dev/null
+++ b/basic-contracts/src/main/java/io/github/jonloucks/examples/contracts/basic/QuitCommand.java
@@ -0,0 +1,21 @@
+package io.github.jonloucks.examples.contracts.basic;
+
+import static io.github.jonloucks.contracts.api.GlobalContracts.claimContract;
+import static io.github.jonloucks.examples.contracts.basic.Constants.IS_QUITTING;
+
+final class QuitCommand implements Command {
+ @Override
+ public String execute() {
+ claimContract(IS_QUITTING).countDown();
+ return "Quit initiated";
+ }
+
+ @Override
+ public boolean foreground() {
+ return true;
+ }
+
+ QuitCommand() {
+
+ }
+}
diff --git a/basic-contracts/src/main/java/module-info.java b/basic-contracts/src/main/java/module-info.java
new file mode 100644
index 0000000..d88a09a
--- /dev/null
+++ b/basic-contracts/src/main/java/module-info.java
@@ -0,0 +1,5 @@
+module io.github.jonloucks.examples.contracts.basic {
+ requires transitive io.github.jonloucks.contracts;
+
+ exports io.github.jonloucks.examples.contracts.basic;
+}
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index de69431..e5a05d5 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -1,2 +1,3 @@
rootProject.name = 'examples'
-include 'basic-contracts'
\ No newline at end of file
+include 'basic-contracts'
+include 'basic-concurrency'
\ No newline at end of file