├── .gitignore ├── AppendixB ├── Keeper.java ├── Otter.java ├── OtterApp.java ├── README.md └── StringStream.java ├── Ch01 ├── Ch01.java ├── HTTP2Check ├── README.md ├── SwitchItUp.java └── var │ └── Var.java ├── Ch02 ├── README.md ├── StackTraceDemo.java ├── src │ └── ch02 │ │ ├── MyEncoder.java │ │ ├── MyURLHandler.java │ │ ├── MyUnsafe.java │ │ └── ReflBytecodeName.java ├── wgjd.discovery │ ├── module-info.java │ └── wgjd │ │ └── discovery │ │ ├── Discovery.java │ │ ├── VMIntrospector.java │ │ └── internal │ │ ├── AttachOutput.java │ │ └── PlainAttachOutput.java ├── wgjd.multi-version │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── wgjd2ed │ │ │ ├── GetPID.java │ │ │ └── Main.java │ └── versions │ │ └── 11 │ │ └── java │ │ └── wgjd2ed │ │ └── GetPID.java └── wgjd.sitecheck │ ├── module-info.java │ └── wgjd │ └── sitecheck │ ├── HTTPChecker.java │ ├── SiteCheck.java │ ├── concurrent │ └── ParallelHTTPChecker.java │ └── internal │ ├── PushHTTPChecker.java │ └── TrustEveryone.java ├── Ch04 ├── ExampleNoClassDef.java ├── LoadSomeClasses.java ├── Main.java ├── MyClass.java ├── NativeMethodChecker.java └── README.md ├── Ch05 ├── README.md └── ch05 │ ├── Account.java │ ├── BadThread.java │ ├── Builder.java │ ├── Deposit.java │ ├── FSOAccount.java │ ├── FSOMain.java │ ├── LifecycleWithInterrupt.java │ ├── SafeAccount.java │ ├── SafeLifecycleWithInterrupt.java │ ├── SimpleLifecycle.java │ ├── StringStack.java │ └── TaskManager.java ├── Ch06 ├── README.md └── ch06 │ ├── BadMapExamples.java │ ├── CDLExamples.java │ ├── CFExamples.java │ ├── COWExamples.java │ ├── Dictionary.java │ ├── ExecutorExamples.java │ ├── FutureExamples.java │ ├── ImmutableDictionary.java │ ├── RWLockExamples.java │ ├── SynchronizedDictionary.java │ ├── TaskManager.java │ └── accounts │ ├── Account.java │ ├── AccountManager.java │ ├── Main.java │ └── TransferTask.java ├── Ch07 ├── README.md └── ch07 │ └── CacheTester.java ├── Ch09 ├── BasicFunctionDeclarations.kts ├── Collections.kts ├── Companions.kts ├── DataClasses.kts ├── Expressions.kts ├── FirstClassFunctions.kts ├── Interfaces.kts ├── NamedArguments.kts ├── Nullability.kts ├── Person.kts ├── PersonWithProperties.kts ├── README.md ├── SmartCasting.kts ├── TypeDeclarationsAndEquality.kts ├── coroutines-app │ ├── README.md │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ ├── CancellingMain.kt │ │ ├── CoopMain.kt │ │ └── Main.kt └── interoperability │ ├── README.md │ ├── build.gradle.kts │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ └── main │ ├── java │ └── Main.java │ └── kotlin │ ├── Main.kt │ └── Person.kt ├── Ch10 ├── README.md ├── examples │ ├── EmptyIterator.java │ ├── IntGeneratorSeq.java │ ├── Main.java │ ├── SeqExamples.java │ └── SquareSeq.java └── lang │ ├── ArraySeq.java │ ├── IFn.java │ ├── ISeq.java │ ├── Keyword.java │ ├── MapBase.java │ ├── Namespace.java │ ├── Symbol.java │ ├── Utils.java │ └── Var.java ├── Ch11 ├── README.md ├── gradle-ee-11 │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── gradle-ee-8 │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── gradle-example │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── wellgrounded │ │ │ │ └── Main.java │ │ └── kotlin │ │ │ └── com │ │ │ └── wellgrounded │ │ │ └── kotlin │ │ │ └── MessageFromKotlin.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── MainTest.java ├── gradle-jlink │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── wgjd.discovery │ │ ├── module-info.java │ │ └── wgjd │ │ └── discovery │ │ ├── Discovery.java │ │ ├── VMIntrospector.java │ │ └── internal │ │ ├── AttachOutput.java │ │ └── PlainAttachOutput.java ├── gradle-modules │ ├── mod-app │ │ ├── build.gradle.kts │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── com.wellgrounded.modapp │ │ │ └── java │ │ │ ├── com │ │ │ └── wellgrounded │ │ │ │ └── Main.java │ │ │ └── module-info.java │ ├── mod-lib │ │ ├── build.gradle.kts │ │ ├── gradle │ │ │ └── wrapper │ │ │ │ ├── gradle-wrapper.jar │ │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── src │ │ │ └── com.wellgrounded.modlib │ │ │ └── java │ │ │ ├── com │ │ │ └── wellgrounded │ │ │ │ ├── hidden │ │ │ │ └── CantTouchThis.java │ │ │ │ └── visible │ │ │ │ └── UseThis.java │ │ │ └── module-info.java │ └── non-app │ │ ├── build.gradle.kts │ │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ │ ├── gradlew │ │ ├── gradlew.bat │ │ ├── settings.gradle.kts │ │ └── src │ │ └── com.wellgrounded.nonapp │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── maven-ee-11 │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── maven-ee-8 │ ├── pom.xml │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── maven-example │ ├── pom.xml │ └── src │ │ ├── main │ │ ├── java │ │ │ └── com │ │ │ │ └── wellgrounded │ │ │ │ └── Main.java │ │ └── kotlin │ │ │ └── com │ │ │ └── wellgrounded │ │ │ └── MessageFromKotlin.kt │ │ └── test │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ ├── MainTest.java │ │ └── integration │ │ └── LongRunningIT.java ├── maven-modules │ ├── mod-app │ │ ├── pom.xml │ │ └── src │ │ │ └── com.wellgrounded.modapp │ │ │ └── java │ │ │ ├── com │ │ │ └── wellgrounded │ │ │ │ └── Main.java │ │ │ └── module-info.java │ ├── mod-lib │ │ ├── pom.xml │ │ └── src │ │ │ └── com.wellgrounded.modlib │ │ │ └── java │ │ │ ├── com │ │ │ └── wellgrounded │ │ │ │ ├── hidden │ │ │ │ └── CantTouchThis.java │ │ │ │ └── visible │ │ │ │ └── UseThis.java │ │ │ └── module-info.java │ └── non-app │ │ ├── back-to-classpath.sh │ │ ├── pom.xml │ │ └── src │ │ └── com.wellgrounded.nonapp │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── maven-multi-release │ ├── pom.xml │ ├── src │ │ └── main │ │ │ └── java │ │ │ └── wgjd2ed │ │ │ ├── GetPID.java │ │ │ └── Main.java │ └── versions │ │ └── 11 │ │ └── src │ │ └── wgjd2ed │ │ └── GetPID.java └── wellgrounded-maven-plugin │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── wellgrounded │ └── WellGroundedMojo.java ├── Ch12 ├── README.md ├── docker-gradle-multi │ ├── .dockerignore │ ├── Dockerfile │ ├── README.md │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java ├── docker-gradle │ ├── Dockerfile │ ├── README.md │ ├── build.gradle.kts │ ├── docker-compose.yml │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ └── Main.java └── docker-maven │ ├── Dockerfile │ ├── README.md │ ├── pom.xml │ └── src │ └── main │ └── java │ └── com │ └── wellgrounded │ └── Main.java ├── Ch13 ├── README.md ├── gradle-junit-4 │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── Main.java │ │ │ └── PasswordChecker.java │ │ └── test │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ ├── MainTest.java │ │ └── PasswordCheckerTest.java ├── gradle-junit-5 │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── Main.java │ │ │ └── PasswordChecker.java │ │ └── test │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ ├── MainTest.java │ │ ├── PasswordCheckerExtension.java │ │ └── PasswordCheckerTest.java ├── maven-junit-4 │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── Main.java │ │ │ └── PasswordChecker.java │ │ └── test │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ ├── MainTest.java │ │ └── PasswordCheckerTest.java ├── maven-junit-5 │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── Main.java │ │ │ └── PasswordChecker.java │ │ └── test │ │ └── java │ │ └── com │ │ └── wellgrounded │ │ ├── MainTest.java │ │ ├── PasswordCheckerExtension.java │ │ └── PasswordCheckerTest.java └── ticketing │ ├── build.gradle.kts │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ ├── main │ └── java │ │ └── com │ │ └── wellgrounded │ │ ├── HttpPrice.java │ │ ├── HttpPricingService.java │ │ ├── Main.java │ │ ├── Price.java │ │ ├── Show.java │ │ ├── Ticket.java │ │ ├── TicketDatabase.java │ │ └── TicketRevenue.java │ └── test │ └── java │ └── com │ └── wellgrounded │ ├── FakeTicketDatabase.java │ ├── MainTest.java │ ├── ShowTest.java │ ├── StubPrice.java │ ├── TicketRevenueTest.java │ └── TicketTest.java ├── Ch14 ├── README.md ├── clj-testing │ ├── README.md │ ├── bin │ │ ├── cognitect-test │ │ └── kaocha │ ├── deps.edn │ ├── test │ │ └── first_test.clj │ └── tests.edn ├── spek-gradle │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── HttpPrice.java │ │ │ ├── HttpPricingService.java │ │ │ ├── InMemoryCachedPrice.java │ │ │ ├── Main.java │ │ │ └── Price.java │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── wellgrounded │ │ ├── InMemoryCachedPriceSpek.kt │ │ ├── InMemoryCachedPriceSpekGherkin.kt │ │ ├── InMemoryCachedPriceSpekSpecification.kt │ │ └── StubPrice.kt ├── spek-maven │ ├── pom.xml │ └── src │ │ ├── main │ │ └── java │ │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── HttpPrice.java │ │ │ ├── HttpPricingService.java │ │ │ ├── InMemoryCachedPrice.java │ │ │ ├── Main.java │ │ │ └── Price.java │ │ └── test │ │ └── kotlin │ │ └── com │ │ └── wellgrounded │ │ ├── InMemoryCachedPriceSpek.kt │ │ ├── InMemoryCachedPriceSpekGherkin.kt │ │ ├── InMemoryCachedPriceSpekSpecification.kt │ │ └── StubPrice.kt └── testcontainers │ ├── build.gradle.kts │ ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ ├── main │ ├── java │ │ └── com │ │ │ └── wellgrounded │ │ │ ├── CachedPrice.java │ │ │ ├── HttpPrice.java │ │ │ ├── HttpPricingService.java │ │ │ ├── Main.java │ │ │ └── Price.java │ └── resources │ │ └── init.sql │ └── test │ └── java │ └── com │ └── wellgrounded │ ├── CachedPriceTest.java │ ├── PostgresTest.java │ ├── StubPrice.java │ └── WebTest.java ├── Ch15 ├── README.md ├── build.gradle.kts ├── gradle │ └── wrapper │ │ ├── gradle-wrapper.jar │ │ └── gradle-wrapper.properties ├── gradlew ├── gradlew.bat ├── settings.gradle.kts └── src │ └── main │ └── java │ └── ch15 │ ├── Account.java │ ├── BiFunction.java │ ├── Builder.java │ ├── Ch15Examples.java │ ├── ClosureExamples.java │ ├── DaySupplier.java │ ├── Deposit.java │ ├── DepositMain.java │ ├── Main.java │ ├── Prefixer.java │ ├── PrefixerMain.java │ ├── PrefixerOld.java │ ├── StreamExamples.java │ ├── StringApplier.java │ ├── TailRecASM.java │ └── TailRecNaive.java ├── Ch16 ├── README.md ├── coroutines-app │ ├── build.gradle.kts │ ├── coroutines-app.bytecode │ ├── dump │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── kotlin │ │ └── Main.kt └── src │ └── ch16 │ ├── Account.java │ ├── CFExamples.java │ ├── NumberService.java │ ├── SorterMain.java │ ├── Transaction.java │ ├── TransactionSorter.java │ └── clj │ ├── PersistentExamples.java │ └── PersistentVector.java ├── Ch17 ├── README.md └── ch17 │ ├── Account.java │ ├── AtomicAccount.java │ ├── Concat.java │ ├── ExamplePrivate.java │ ├── HiddenExamples.java │ ├── MHExamples.java │ ├── ReflectionExamples.java │ ├── SynchronizedAccount.java │ └── VHAccount.java ├── Ch18 ├── Panama │ ├── .gitattributes │ ├── .gitignore │ ├── build.gradle.kts │ ├── gradle │ │ └── wrapper │ │ │ ├── gradle-wrapper.jar │ │ │ └── gradle-wrapper.properties │ ├── gradlew │ ├── gradlew.bat │ ├── settings.gradle.kts │ └── src │ │ └── main │ │ └── java │ │ └── wgjd.png │ │ ├── PngReader.java │ │ └── module-info.java ├── README.md └── ch18 │ ├── AmberExamples.java │ ├── Java17Examples.java │ ├── SwitchExamples.java │ └── fx │ ├── CurrencyPair.java │ ├── FXOrderClassic.java │ ├── OrderPartition.java │ ├── OrderType.java │ ├── Side.java │ ├── records │ └── FXOrder.java │ └── sealed │ ├── FXAccepted.java │ ├── FXCancelled.java │ ├── FXFill.java │ ├── FXOrder.java │ ├── FXOrderResponse.java │ ├── FXReject.java │ ├── LimitOrder.java │ ├── MarketOrder.java │ ├── OrderPatterns.java │ └── Pet.java ├── Charts └── charts │ └── Main.java ├── Misc └── misc │ ├── FindJavaVisitor.java │ └── MiscFiles.java └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | # Compiled class file 2 | *.class 3 | 4 | # Log file 5 | *.log 6 | 7 | # BlueJ files 8 | *.ctxt 9 | 10 | # Mobile Tools for Java (J2ME) 11 | .mtj.tmp/ 12 | 13 | # Package Files # 14 | *.war 15 | *.nar 16 | *.ear 17 | *.zip 18 | *.tar.gz 19 | *.rar 20 | 21 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 22 | hs_err_pid* 23 | 24 | # IntelliJ 25 | .idea/ 26 | out/ 27 | build/ 28 | *.iml 29 | 30 | # Gradle local 31 | .gradle/ 32 | 33 | # Maven 34 | target/ 35 | 36 | # Clojure 37 | .cpcache/ 38 | 39 | # tmp 40 | tmp/ 41 | -------------------------------------------------------------------------------- /AppendixB/Keeper.java: -------------------------------------------------------------------------------- 1 | public class Keeper { 2 | private boolean trainee; 3 | 4 | public boolean isTrainee() { 5 | return trainee; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /AppendixB/Otter.java: -------------------------------------------------------------------------------- 1 | public class Otter { 2 | private boolean wild; 3 | private Keeper keeper; 4 | private int age; 5 | 6 | public boolean isWild() { 7 | return wild; 8 | } 9 | 10 | public Keeper getKeeper() { 11 | return keeper; 12 | } 13 | 14 | public void setKeeper(Keeper keeper) { 15 | this.keeper = keeper; 16 | } 17 | 18 | public void incAge() { 19 | this.age++; 20 | } 21 | 22 | public int getAge() { 23 | return age; 24 | } 25 | } 26 | -------------------------------------------------------------------------------- /AppendixB/OtterApp.java: -------------------------------------------------------------------------------- 1 | import java.util.Set; 2 | import java.util.stream.Collectors; 3 | 4 | public class OtterApp { 5 | private static Keeper kate = new Keeper(); 6 | private static Keeper bob = new Keeper(); 7 | 8 | public static void main(String[] args) { 9 | // Otters 10 | Set ots = getOtters(); 11 | System.out.println(ots.stream() 12 | .filter(o -> !o.isWild()) 13 | .map(o -> o.getKeeper()) 14 | .filter(k -> k.isTrainee()) 15 | .collect(Collectors.toList()) 16 | .size()); 17 | 18 | // Otter average age 19 | double aveAge = ((double) ots.stream() 20 | .map(o -> o.getAge()) 21 | .reduce(0, (x, y) -> x + y)) / ots.size(); 22 | System.out.println("Average age: "+ aveAge); 23 | 24 | // Bob the backup 25 | ots.stream() 26 | .filter(o -> !o.isWild()) 27 | .filter(o -> o.getKeeper().equals(kate)) 28 | .forEach(o -> o.setKeeper(bob)); 29 | } 30 | 31 | private static Set getOtters() { 32 | var splash = new Otter(); 33 | 34 | splash.incAge(); 35 | splash.setKeeper(kate); 36 | 37 | return Set.of(splash); 38 | } 39 | } 40 | -------------------------------------------------------------------------------- /AppendixB/README.md: -------------------------------------------------------------------------------- 1 | # Appendix B - Recap of streams in Java 8 2 | 3 | ## `AppendixB` 4 | 5 | This directory contains individual files which may be compiled without 6 | external dependencies. These examples should be used with Java 11+. 7 | 8 | * `StringStream.java` - Basic streaming filter and collect example 9 | * `OtterApp.java` - Example application using custom classes. Needs `Otter.java` and `Keeper.java` compiled. 10 | * `Otter.java` - Class definition for use in `OtterApp.java` 11 | * `Keeper.java` - Class definition for use in `OtterApp.java` 12 | 13 | These examples may be loaded and run individually in an IDE. You may need to 14 | set the project JDK version. 15 | 16 | To compile and run these samples at the command-line, do the following: 17 | 18 | ``` 19 | cd AppendixB 20 | javac *.java 21 | java StringStream 22 | ``` 23 | -------------------------------------------------------------------------------- /AppendixB/StringStream.java: -------------------------------------------------------------------------------- 1 | import java.util.List; 2 | import java.util.stream.Collectors; 3 | 4 | public class StringStream { 5 | public static void main(String[] args) { 6 | // Example find 7 | List myStrings = getSomeStrings(); 8 | String search = getSearchString(); 9 | 10 | System.out.println(myStrings.stream() 11 | .filter(s -> s.equals(search)) 12 | .collect(Collectors.toList())); 13 | } 14 | 15 | private static List getSomeStrings() { 16 | return List.of( 17 | "this", 18 | "is", 19 | "a", 20 | "list" 21 | ); 22 | } 23 | 24 | private static String getSearchString() { 25 | return "list"; 26 | } 27 | } -------------------------------------------------------------------------------- /Ch01/Ch01.java: -------------------------------------------------------------------------------- 1 | import java.util.*; 2 | import java.util.function.*; 3 | import var.Var; 4 | import static var.Var.var; 5 | 6 | public class Ch01 { 7 | 8 | public static void main(String[] args) { 9 | Map> usersLists = new HashMap<>(); 10 | 11 | Function lengthFn = s -> s.length(); 12 | 13 | var var = var(); 14 | if (var == null) { 15 | var(new Var()); 16 | } 17 | System.out.println(var()); 18 | } 19 | 20 | } 21 | -------------------------------------------------------------------------------- /Ch01/README.md: -------------------------------------------------------------------------------- 1 | # Ch01 - Introducing modern Java 2 | 3 | ## `HTTP2Check` 4 | 5 | `HTTP2Check` should be run on Java 11 or higher. It may be run directly in a 6 | command-line. 7 | 8 | ``` 9 | ./HTTP2Check https://github.com/well-grounded-java/resources 10 | ``` 11 | 12 | ## `Ch01` - `var` 13 | 14 | `Ch01.java` should be run on Java 17 and requires classes from the `var` 15 | directory. It is simplest to load and run this example in an IDE. 16 | 17 | ## `SwitchItUp` - `switch` expressions 18 | 19 | `SwitchItUp.java` should be run on Java 17. As it has no dependencies on other 20 | classes it may be compiled and run at the command-line. It is also easily 21 | runnable when loaded in an IDE. 22 | 23 | ``` 24 | javac SwitchItUp.java 25 | java SwitchItUp 26 | ``` 27 | -------------------------------------------------------------------------------- /Ch01/var/Var.java: -------------------------------------------------------------------------------- 1 | package var; 2 | 3 | public class Var { 4 | 5 | public static Var var() { 6 | return new Var(); 7 | } 8 | 9 | public static Var var(Var var) { 10 | return new Var(); 11 | } 12 | 13 | } 14 | -------------------------------------------------------------------------------- /Ch02/StackTraceDemo.java: -------------------------------------------------------------------------------- 1 | public class StackTraceDemo { 2 | public static void main(String[] args) { 3 | var i = Integer.parseInt("Fail"); 4 | } 5 | } -------------------------------------------------------------------------------- /Ch02/src/ch02/MyEncoder.java: -------------------------------------------------------------------------------- 1 | package ch02; 2 | 3 | import java.io.IOException; 4 | import java.io.OutputStream; 5 | 6 | import sun.misc.BASE64Encoder; 7 | 8 | public class MyEncoder extends BASE64Encoder { 9 | 10 | public void encodeAtom(OutputStream outStream, byte data[]) 11 | throws IOException { 12 | encodeAtom(outStream, data, 0, data.length); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch02/src/ch02/MyURLHandler.java: -------------------------------------------------------------------------------- 1 | package ch02; 2 | 3 | import sun.net.URLCanonicalizer; 4 | 5 | public class MyURLHandler extends URLCanonicalizer { 6 | 7 | public boolean isSimple(String url) { 8 | return isSimpleHostName(url); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch02/src/ch02/MyUnsafe.java: -------------------------------------------------------------------------------- 1 | import sun.misc.Unsafe; 2 | 3 | import java.lang.reflect.Field; 4 | 5 | public class MyUnsafe { 6 | public static void main(String[] args) { 7 | MyUnsafe self = new MyUnsafe(); 8 | try { 9 | self.run(); 10 | } catch (Exception e) { 11 | e.printStackTrace(); 12 | } 13 | } 14 | 15 | private void run() throws Exception { 16 | Field f = Unsafe.class.getDeclaredField("theUnsafe"); 17 | f.setAccessible(true); 18 | Unsafe unsafe = (Unsafe) f.get(null); 19 | System.out.println(unsafe); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ch02/src/ch02/ReflBytecodeName.java: -------------------------------------------------------------------------------- 1 | package ch02; 2 | 3 | import java.lang.reflect.Method; 4 | import java.util.Arrays; 5 | 6 | public class ReflBytecodeName { 7 | 8 | public static void main(String[] args) { 9 | ReflBytecodeName refl = new ReflBytecodeName(); 10 | try { 11 | refl.run(); 12 | } catch (Exception e) { 13 | e.printStackTrace(); 14 | } 15 | } 16 | 17 | private synchronized void run() throws Exception { 18 | // Exception handling elided 19 | Class clz = Class.forName("sun.invoke.util.BytecodeName"); 20 | Method method = clz.getDeclaredMethod("parseBytecodeName", String.class); 21 | Object res = method.invoke(null, "java/lang/String"); 22 | System.out.println(Arrays.toString((Object[])res)); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch02/wgjd.discovery/module-info.java: -------------------------------------------------------------------------------- 1 | module wgjd.discovery { 2 | exports wgjd.discovery; 3 | 4 | requires java.instrument; 5 | requires java.logging; 6 | requires jdk.attach; 7 | requires jdk.internal.jvmstat; 8 | } 9 | -------------------------------------------------------------------------------- /Ch02/wgjd.discovery/wgjd/discovery/Discovery.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery; 2 | 3 | import com.sun.tools.attach.VirtualMachine; 4 | import com.sun.tools.attach.VirtualMachineDescriptor; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | import wgjd.discovery.internal.PlainAttachOutput; 10 | 11 | public class Discovery { 12 | private static final Set PROCESS_SKIP_TERMS = 13 | Set.of("sun.tools.jconsole.JConsole", "gradle"); 14 | 15 | public static void main(String[] args) { 16 | var output = new PlainAttachOutput(); 17 | 18 | try { 19 | final var vmConsumer = new VMIntrospector(); 20 | System.out.println("Java processes:"); 21 | System.out.println("PID\tDisplay Name\tVM Version\tAttachable"); 22 | 23 | final List vmds = VirtualMachine.list(); 24 | for (var vmd : vmds) { 25 | if (!skip(vmd)) { 26 | vmConsumer.accept(vmd); 27 | } 28 | } 29 | } finally { 30 | output.finished(); 31 | } 32 | } 33 | 34 | private static boolean skip(VirtualMachineDescriptor vmd) { 35 | final var displayName = vmd.displayName(); 36 | for (var term : PROCESS_SKIP_TERMS) { 37 | if (displayName.contains(term)) { 38 | return true; 39 | } 40 | } 41 | return // REVIEW not sure if we can skip all of these 42 | displayName.isEmpty(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Ch02/wgjd.discovery/wgjd/discovery/VMIntrospector.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery; 2 | 3 | import com.sun.tools.attach.VirtualMachineDescriptor; 4 | import sun.jvmstat.monitor.MonitorException; 5 | import sun.jvmstat.monitor.MonitoredHost; 6 | import sun.jvmstat.monitor.MonitoredVmUtil; 7 | import sun.jvmstat.monitor.VmIdentifier; 8 | 9 | import java.net.URISyntaxException; 10 | import java.util.function.Consumer; 11 | 12 | public class VMIntrospector implements Consumer { 13 | 14 | @Override 15 | public void accept(VirtualMachineDescriptor vmd) { 16 | var isAttachable = false; 17 | var vmVersion = ""; 18 | try { 19 | var vmId = new VmIdentifier(vmd.id()); 20 | var monitoredHost = MonitoredHost.getMonitoredHost(vmId); 21 | var monitoredVm = monitoredHost.getMonitoredVm(vmId, -1); 22 | try { 23 | isAttachable = MonitoredVmUtil.isAttachable(monitoredVm); 24 | vmVersion = MonitoredVmUtil.vmVersion(monitoredVm); 25 | } finally { 26 | monitoredHost.detach(monitoredVm); 27 | } 28 | } catch (URISyntaxException | MonitorException e) { 29 | e.printStackTrace(); 30 | } 31 | 32 | System.out.println( 33 | vmd.id() + '\t' + vmd.displayName() + '\t' + vmVersion + '\t' + isAttachable); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ch02/wgjd.discovery/wgjd/discovery/internal/AttachOutput.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery.internal; 2 | 3 | public interface AttachOutput { 4 | void attachStarted(String pid, String command, String agentArgs); 5 | 6 | void attachFinished(); 7 | 8 | void finished(); 9 | 10 | void error(Exception e); 11 | 12 | void warn(String message); 13 | } 14 | -------------------------------------------------------------------------------- /Ch02/wgjd.discovery/wgjd/discovery/internal/PlainAttachOutput.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery.internal; 2 | 3 | import java.io.PrintStream; 4 | 5 | public class PlainAttachOutput implements AttachOutput { 6 | private final PrintStream out; 7 | private final PrintStream err; 8 | 9 | public PlainAttachOutput() { 10 | this(System.out, System.err); 11 | } 12 | 13 | public PlainAttachOutput(PrintStream out, PrintStream err) { 14 | this.out = out; 15 | this.err = err; 16 | } 17 | 18 | @Override 19 | public void attachStarted(String pid, String command, String agentArgs) { 20 | out.println("\n--- Attaching ---"); 21 | out.println("\tPID "+ pid); 22 | out.println("\tCommand "+ command); 23 | } 24 | 25 | @Override 26 | public void finished() { 27 | out.flush(); 28 | err.flush(); 29 | } 30 | 31 | @Override 32 | public void attachFinished() {} 33 | 34 | @Override 35 | public void error(Exception e) { 36 | e.printStackTrace(err); 37 | } 38 | 39 | @Override 40 | public void warn(String message) {} 41 | } 42 | -------------------------------------------------------------------------------- /Ch02/wgjd.multi-version/src/main/java/wgjd2ed/GetPID.java: -------------------------------------------------------------------------------- 1 | package wgjd2ed; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | /** 6 | * Java 8 version of GetPID class 7 | * 8 | * @author ben 9 | */ 10 | public class GetPID { 11 | 12 | public static long getPid() { 13 | System.out.println("Java 8 version..."); 14 | // ManagementFactory.getRuntimeMXBean().getName() returns the name that 15 | // represents the currently running JVM. On Sun and Oracle JVMs, this 16 | // name is in the format @. 17 | 18 | final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); 19 | final int index = jvmName.indexOf('@'); 20 | if (index < 1) 21 | return 0; 22 | 23 | try { 24 | return Long.parseLong(jvmName.substring(0, index)); 25 | } catch (NumberFormatException nfe) { 26 | return 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ch02/wgjd.multi-version/src/main/java/wgjd2ed/Main.java: -------------------------------------------------------------------------------- 1 | package wgjd2ed; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) { 6 | System.out.println(GetPID.getPid()); 7 | } 8 | } 9 | 10 | -------------------------------------------------------------------------------- /Ch02/wgjd.multi-version/versions/11/java/wgjd2ed/GetPID.java: -------------------------------------------------------------------------------- 1 | package wgjd2ed; 2 | 3 | /** 4 | * Java 9 GetPID class 5 | * 6 | * @author ben 7 | */ 8 | public class GetPID { 9 | 10 | public static long getPid() { 11 | var ph = ProcessHandle.current(); 12 | return ph.pid(); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch02/wgjd.sitecheck/module-info.java: -------------------------------------------------------------------------------- 1 | module wgjd.sitecheck { 2 | requires java.net.http; 3 | 4 | exports wgjd.sitecheck; 5 | exports wgjd.sitecheck.concurrent; 6 | } 7 | -------------------------------------------------------------------------------- /Ch02/wgjd.sitecheck/wgjd/sitecheck/HTTPChecker.java: -------------------------------------------------------------------------------- 1 | package wgjd.sitecheck; 2 | 3 | import java.io.IOException; 4 | import java.net.URISyntaxException; 5 | 6 | public interface HTTPChecker { 7 | public void check(String location) throws IOException, URISyntaxException, InterruptedException; 8 | } 9 | -------------------------------------------------------------------------------- /Ch02/wgjd.sitecheck/wgjd/sitecheck/concurrent/ParallelHTTPChecker.java: -------------------------------------------------------------------------------- 1 | package wgjd.sitecheck.concurrent; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.net.http.*; 5 | import java.io.IOException; 6 | import java.net.URI; 7 | import java.net.URISyntaxException; 8 | import wgjd.sitecheck.HTTPChecker; 9 | 10 | public class ParallelHTTPChecker implements HTTPChecker { 11 | private final HttpClient client; 12 | 13 | public ParallelHTTPChecker(HttpClient cl) { 14 | client = cl; 15 | } 16 | 17 | @Override 18 | public void check(String location) throws IOException, URISyntaxException, InterruptedException { 19 | var uri = new URI(location); 20 | var req = HttpRequest.newBuilder(uri).build(); 21 | 22 | var handler = HttpResponse.BodyHandlers.ofString(); 23 | CompletableFuture.allOf( 24 | client.sendAsync(req, handler) 25 | .thenAccept((resp) -> System.out.println("First")), 26 | client.sendAsync(req, handler) 27 | .thenAccept((resp) -> System.out.println("Second")), 28 | client.sendAsync(req, handler) 29 | .thenAccept((resp) -> System.out.println("Third")) 30 | ).join(); 31 | } 32 | 33 | 34 | 35 | } 36 | -------------------------------------------------------------------------------- /Ch02/wgjd.sitecheck/wgjd/sitecheck/internal/TrustEveryone.java: -------------------------------------------------------------------------------- 1 | package wgjd.sitecheck.internal; 2 | 3 | import javax.net.ssl.SSLContext; 4 | import javax.net.ssl.TrustManager; 5 | import javax.net.ssl.X509TrustManager; 6 | import java.security.cert.CertificateException; 7 | import java.security.cert.X509Certificate; 8 | import java.security.KeyManagementException; 9 | import java.security.NoSuchAlgorithmException; 10 | import java.security.SecureRandom; 11 | 12 | public class TrustEveryone implements X509TrustManager { 13 | @Override 14 | public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException { 15 | } 16 | 17 | @Override 18 | public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException { 19 | } 20 | 21 | @Override 22 | public X509Certificate[] getAcceptedIssuers() { 23 | return new X509Certificate[0]; 24 | } 25 | 26 | public static SSLContext context() throws NoSuchAlgorithmException, KeyManagementException { 27 | // I couldn't find any actual site that was doing H2 server push to test :/ 28 | // There's a nice little local server for it at https://github.com/GoogleChromeLabs/simplehttp2server 29 | // BUT because it runs on localhost we have to convince HttpClient that no, 30 | // we really want to just trust it.... 31 | var trustEveryone = new TrustManager[] { new TrustEveryone() }; 32 | var sslContext = SSLContext.getInstance("SSL"); 33 | sslContext.init(null, trustEveryone, new SecureRandom()); 34 | 35 | return sslContext; 36 | } 37 | } 38 | 39 | -------------------------------------------------------------------------------- /Ch04/ExampleNoClassDef.java: -------------------------------------------------------------------------------- 1 | public class ExampleNoClassDef { 2 | 3 | public static class BadInit { 4 | private static int thisIsFine = 1 / 0; 5 | } 6 | 7 | public static void main(String[] args) { 8 | try { 9 | var init = new BadInit(); 10 | } catch (Throwable t) { 11 | System.out.println(t); 12 | } 13 | var init2 = new BadInit(); 14 | System.out.println(init2.thisIsFine); 15 | } 16 | } -------------------------------------------------------------------------------- /Ch04/LoadSomeClasses.java: -------------------------------------------------------------------------------- 1 | public class LoadSomeClasses { 2 | 3 | public static class SadClassloader extends ClassLoader { 4 | public SadClassloader() { 5 | super(SadClassloader.class.getClassLoader()); 6 | } 7 | 8 | public Class findClass(String name) throws ClassNotFoundException { 9 | System.out.println("I am very concerned that I couldn't find the class"); 10 | throw new ClassNotFoundException(name); 11 | } 12 | } 13 | 14 | public static void main(String[] args) { 15 | if (args.length > 0) { 16 | var loader = new SadClassloader(); 17 | for (var name : args) { 18 | System.out.println(name +" ::"); 19 | try { 20 | var clazz = loader.loadClass(name); 21 | System.out.println(clazz); 22 | } catch (ClassNotFoundException x) { 23 | x.printStackTrace(); 24 | } 25 | } 26 | } 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Ch04/Main.java: -------------------------------------------------------------------------------- 1 | public class Main { 2 | 3 | public static void main(String[] args) { 4 | try { 5 | Class clazz = Class.forName("MyClass"); 6 | System.out.println(clazz); 7 | } catch (ClassNotFoundException e) { 8 | e.printStackTrace(); 9 | } 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch04/MyClass.java: -------------------------------------------------------------------------------- 1 | public class MyClass { 2 | } 3 | -------------------------------------------------------------------------------- /Ch04/NativeMethodChecker.java: -------------------------------------------------------------------------------- 1 | import java.io.IOException; 2 | import java.lang.reflect.Modifier; 3 | import java.nio.file.Files; 4 | import java.nio.file.Path; 5 | 6 | public class NativeMethodChecker { 7 | public static class EasyLoader extends ClassLoader { 8 | public EasyLoader() { 9 | super(EasyLoader.class.getClassLoader()); 10 | } 11 | 12 | public Class loadFromDisk(String fName) throws IOException { 13 | var b = Files.readAllBytes(Path.of(fName)); 14 | return defineClass(null, b, 0, b.length); 15 | } 16 | } 17 | 18 | public static void main(String[] args) { 19 | if (args.length > 0) { 20 | var loader = new EasyLoader(); 21 | for (var file : args) { 22 | System.out.println(file +" ::"); 23 | try { 24 | var clazz = loader.loadFromDisk(file); 25 | for (var m : clazz.getMethods()) { 26 | if (Modifier.isNative(m.getModifiers())) { 27 | System.out.println(m.getName()); 28 | } 29 | } 30 | } catch (IOException | ClassFormatError x) { 31 | System.out.println("Not a class file"); 32 | } 33 | } 34 | } 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Ch04/README.md: -------------------------------------------------------------------------------- 1 | # Ch04 - Class files and bytecode 2 | 3 | ## `Ch04` 4 | 5 | This directory contains individual files which may be compiled without needing 6 | additional dependencies. These examples should be used with Java 11+. 7 | 8 | * `ExampleNoClassDef.java` - Example of causing failure during class loading 9 | * `LoadSomeClasses.java` - Example of custom classloader 10 | * `Main.java` - Example of dynamically retrieving a `Class` object 11 | * `NativeMethodChecker.java` - Example of custom classloader reading from file 12 | 13 | These examples may be loaded and run individually in an IDE. You may need to 14 | set the project JDK version. `LoadSomeClasses.java` and 15 | `NativeMethodChecker.java` both expect to be passed arguments when executed. 16 | 17 | To compile and run these samples at the command-line, do the following: 18 | 19 | ``` 20 | cd Ch04 21 | javac *.java 22 | java ExampleNoClassDef 23 | ``` 24 | -------------------------------------------------------------------------------- /Ch05/README.md: -------------------------------------------------------------------------------- 1 | # Ch05 - Java concurrency fundamentals 2 | 3 | ## `Ch05/ch05` 4 | 5 | This directory contains a few separate examples. These have no external 6 | dependencies, but may require multiple classes from within the directory. 7 | These examples should be used with Java 11+. 8 | 9 | * `BadThread.java` - Example of uncaught exception handlers from a `Thread`. 10 | * `FSOMain.java` - Concurrent account transfer example. Uses multiple classes in the directory. 11 | * `LifecycleWithInterrupt.java` - Example of `Thread.interrupt` clearing state. 12 | * `SafeLifecycleWithInterrupt.java` - Example of capturing when thread was interrupted. 13 | * `SimpleLifecycle.java` - Example of basic thread lifecycle. 14 | 15 | These examples may be loaded and run in an IDE. You may need to set the project 16 | JDK version. 17 | 18 | To compile and run these samples at the command-line, do the following: 19 | 20 | ``` 21 | cd Ch05/ch05 22 | javac *.java 23 | java -cp .. ch05.SimpleLifecycle 24 | ``` 25 | -------------------------------------------------------------------------------- /Ch05/ch05/BadThread.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class BadThread { 4 | public static void main(String[] args) { 5 | var badThread = new Thread(() -> { 6 | throw new UnsupportedOperationException(); 7 | }); 8 | 9 | // Set a name before starting the thread 10 | badThread.setName("An Exceptional Thread"); 11 | 12 | // Set the handler 13 | badThread.setUncaughtExceptionHandler((t, e) -> { 14 | System.err.printf("Thread %d '%s' has thrown exception " + 15 | "%s at line %d of %s", 16 | t.getId(), 17 | t.getName(), 18 | e.toString(), 19 | e.getStackTrace()[0].getLineNumber(), 20 | e.getStackTrace()[0].getFileName()); }); 21 | 22 | badThread.start(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch05/ch05/Builder.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public interface Builder { 4 | T build(); 5 | } 6 | -------------------------------------------------------------------------------- /Ch05/ch05/FSOAccount.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class FSOAccount { 4 | private double balance; 5 | 6 | public FSOAccount(double openingBalance) { 7 | // Check to see openingBalance > 0, throw if not 8 | balance = openingBalance; 9 | } 10 | 11 | public synchronized boolean withdraw(final int amount) { 12 | // Check to see amount > 0, throw if not 13 | if (balance >= amount) { 14 | balance = balance - amount; 15 | return true; 16 | } 17 | 18 | return false; 19 | } 20 | 21 | public synchronized void deposit(final int amount) { 22 | // Check to see amount > 0, throw if not 23 | balance = balance + amount; 24 | } 25 | 26 | public synchronized double getBalance() { 27 | return balance; 28 | } 29 | 30 | public synchronized boolean transferTo(final FSOAccount other, final int amount) { 31 | // Check to see amount > 0, throw if not 32 | // Simulate some other checks that need to occur 33 | try { 34 | Thread.sleep(10); 35 | } catch (InterruptedException e) { 36 | Thread.currentThread().interrupt(); 37 | } 38 | if (balance >= amount) { 39 | balance = balance - amount; 40 | other.deposit(amount); 41 | return true; 42 | } 43 | 44 | return false; 45 | } 46 | } -------------------------------------------------------------------------------- /Ch05/ch05/FSOMain.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class FSOMain { 4 | private static final int MAX_TRANSFERS = 1_000; 5 | 6 | public static void main(String[] args) throws InterruptedException { 7 | FSOAccount a = new FSOAccount(10_000); 8 | FSOAccount b = new FSOAccount(10_000); 9 | Thread tA = new Thread(() -> { 10 | for (int i = 0; i < MAX_TRANSFERS; i = i + 1) { 11 | boolean ok = a.transferTo(b, 1); 12 | if (!ok) { 13 | System.out.println("Thread A failed at "+ i); 14 | } 15 | } 16 | }); 17 | Thread tB = new Thread(() -> { 18 | for (int i = 0; i < MAX_TRANSFERS; i = i + 1) { 19 | boolean ok = b.transferTo(a, 1); 20 | if (!ok) { 21 | System.out.println("Thread B failed at "+ i); 22 | } 23 | } 24 | }); 25 | tA.start(); 26 | tB.start(); 27 | tA.join(); 28 | tB.join(); 29 | 30 | System.out.println("Finished: "+ a.getBalance() + " : "+ b.getBalance()); 31 | } 32 | } -------------------------------------------------------------------------------- /Ch05/ch05/LifecycleWithInterrupt.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class LifecycleWithInterrupt { 4 | 5 | public static void main(String[] args) throws InterruptedException { 6 | Runnable r = () -> { 7 | var start = System.currentTimeMillis(); 8 | try { 9 | Thread.sleep(1000); 10 | } catch (InterruptedException e) { 11 | e.printStackTrace(); 12 | } 13 | var thisThread = Thread.currentThread(); 14 | System.out.println(thisThread.getName() + 15 | " slept for "+ (System.currentTimeMillis() - start)); 16 | if (thisThread.isInterrupted()) { 17 | System.out.println("Thread "+ thisThread.getName() +" was interrupted"); 18 | } 19 | }; 20 | var t = new Thread(r); 21 | t.setName("Worker"); 22 | t.start(); 23 | Thread.sleep(100); 24 | t.interrupt(); 25 | t.join(); 26 | System.out.println("Exiting"); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Ch05/ch05/SafeLifecycleWithInterrupt.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class SafeLifecycleWithInterrupt { 4 | 5 | public static void main(String[] args) throws InterruptedException { 6 | Runnable r = () -> { 7 | var start = System.currentTimeMillis(); 8 | var wasInterrupted = false; 9 | try { 10 | Thread.sleep(1000); 11 | } catch (InterruptedException e) { 12 | wasInterrupted = true; 13 | e.printStackTrace(); 14 | } 15 | var thisThread = Thread.currentThread(); 16 | System.out.println(thisThread.getName() + 17 | " slept for "+ (System.currentTimeMillis() - start)); 18 | if (wasInterrupted) { 19 | System.out.println("Thread "+ thisThread.getName() +" was interrupted"); 20 | } 21 | }; 22 | var t = new Thread(r); 23 | t.setName("Worker"); 24 | t.start(); 25 | Thread.sleep(100); 26 | t.interrupt(); 27 | t.join(); 28 | System.out.println("Exiting"); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Ch05/ch05/SimpleLifecycle.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class SimpleLifecycle { 4 | 5 | public static void main(String[] args) throws InterruptedException { 6 | Runnable r = () -> { 7 | var start = System.currentTimeMillis(); 8 | try { 9 | Thread.sleep(1000); 10 | } catch (InterruptedException e) { 11 | e.printStackTrace(); 12 | } 13 | var thisThread = Thread.currentThread(); 14 | System.out.println(thisThread.getName() + 15 | " slept for "+ (System.currentTimeMillis() - start)); 16 | }; 17 | var t = new Thread(r); 18 | t.setName("Worker"); 19 | t.start(); 20 | Thread.sleep(100); 21 | t.join(); 22 | System.out.println("Exiting"); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch05/ch05/StringStack.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class StringStack { 4 | private String[] values = new String[16]; 5 | private int current = 0; 6 | 7 | public boolean push(String s) { 8 | if (current < values.length) { 9 | values[current] = s; 10 | current = current + 1; 11 | } 12 | return false; 13 | } 14 | 15 | public String pop() { 16 | if (current < 1) { 17 | return null; 18 | } 19 | current = current - 1; 20 | return values[current]; 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Ch05/ch05/TaskManager.java: -------------------------------------------------------------------------------- 1 | package ch05; 2 | 3 | public class TaskManager implements Runnable { 4 | private volatile boolean shutdown = false; 5 | 6 | public void shutdown() { 7 | shutdown = true; 8 | } 9 | 10 | @Override 11 | public void run() { 12 | while (!shutdown) { 13 | // do some work - e.g. process a work unit 14 | } 15 | } 16 | } -------------------------------------------------------------------------------- /Ch06/ch06/BadMapExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.HashMap; 4 | 5 | public class BadMapExamples { 6 | public static void main(String[] args) throws InterruptedException { 7 | var map = new HashMap(); 8 | var SIZE = 1_000_000; 9 | 10 | Runnable r1 = () -> { 11 | for (int i = 0; i < SIZE; i = i + 1) { 12 | map.put("t1" + i, "0"); 13 | } 14 | System.out.println("Thread 1 done"); 15 | }; 16 | Runnable r2 = () -> { 17 | for (int i = 0; i < SIZE; i = i + 1) { 18 | map.put("t2" + i, "0"); 19 | } 20 | System.out.println("Thread 2 done"); 21 | }; 22 | Thread t1 = new Thread(r1); 23 | Thread t2 = new Thread(r2); 24 | t1.start(); 25 | t2.start(); 26 | t1.join(); 27 | t2.join(); 28 | System.out.println("Count: "+ map.size()); 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Ch06/ch06/CDLExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.concurrent.CountDownLatch; 4 | import java.util.concurrent.atomic.AtomicInteger; 5 | 6 | public class CDLExamples { 7 | public static class Counter implements Runnable { 8 | private final CountDownLatch latch; 9 | private final int value; 10 | private final AtomicInteger count; 11 | 12 | public Counter(CountDownLatch latch, int value, AtomicInteger count) { 13 | this.latch = latch; 14 | this.value = value; 15 | this.count = count; 16 | } 17 | 18 | @Override 19 | public void run() { 20 | try { 21 | Thread.sleep(100); 22 | } catch (InterruptedException e) { 23 | Thread.currentThread().interrupt(); 24 | } 25 | count.addAndGet(value); 26 | latch.countDown(); 27 | } 28 | } 29 | 30 | public static void main(String[] args) throws InterruptedException { 31 | var latch = new CountDownLatch(5); 32 | var count = new AtomicInteger(); 33 | for (int i = 0; i < 5; i = i + 1) { 34 | var r = new Counter(latch, i, count); 35 | new Thread(r).start(); 36 | } 37 | latch.await(); 38 | System.out.println("Total: "+ count.get()); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Ch06/ch06/CFExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.concurrent.CompletableFuture; 4 | import java.util.concurrent.ExecutionException; 5 | 6 | public class CFExamples { 7 | 8 | public static void main(String[] args) { 9 | var numF = new CompletableFuture(); 10 | 11 | new Thread(() -> { 12 | int num = (int) (Integer.MAX_VALUE * Math.random()); 13 | try { 14 | Thread.sleep(2_000); 15 | } catch (InterruptedException e) { 16 | e.printStackTrace(); 17 | } 18 | numF.complete(num); 19 | }).start(); 20 | 21 | try { 22 | Thread.sleep(500); 23 | } catch (InterruptedException e) { 24 | e.printStackTrace(); 25 | } 26 | numF.complete(42); 27 | 28 | try { 29 | System.out.println(numF.get()); 30 | } catch (InterruptedException e) { 31 | e.printStackTrace(); 32 | } catch (ExecutionException e) { 33 | e.printStackTrace(); 34 | } 35 | } 36 | 37 | 38 | } 39 | -------------------------------------------------------------------------------- /Ch06/ch06/COWExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.List; 4 | import java.util.concurrent.CopyOnWriteArrayList; 5 | 6 | public class COWExamples { 7 | 8 | public static void main(String[] args) { 9 | var ls = new CopyOnWriteArrayList(List.of(1, 2, 3)); 10 | var it = ls.iterator(); 11 | ls.add(4); 12 | var modifiedIt = ls.iterator(); 13 | while (it.hasNext()) { 14 | System.out.println("Original: "+ it.next()); 15 | } 16 | while (modifiedIt.hasNext()) { 17 | System.out.println("Modified: "+ modifiedIt.next()); 18 | } 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /Ch06/ch06/ExecutorExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.concurrent.Executors; 4 | 5 | public class ExecutorExamples { 6 | 7 | public static void main(String[] args) { 8 | var pool = Executors.newSingleThreadExecutor(); 9 | Runnable hello = () -> System.out.println("Hello world"); 10 | pool.submit(hello); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Ch06/ch06/FutureExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.concurrent.*; 4 | 5 | public class FutureExamples { 6 | 7 | public static void main(String[] args) throws InterruptedException { 8 | Future fut = getNthPrime(1_000_000_000); 9 | try { 10 | long result = fut.get(1, TimeUnit.MINUTES); 11 | System.out.println("Found it: " + result); 12 | } catch (TimeoutException tox) { 13 | // Timed out - better cancel the task 14 | System.err.println("Task timed out, cancelling"); 15 | fut.cancel(true); 16 | } catch (InterruptedException e) { 17 | fut.cancel(true); 18 | throw e; 19 | } catch (ExecutionException e) { 20 | fut.cancel(true); 21 | e.getCause().printStackTrace(); 22 | } 23 | } 24 | 25 | private static Future getNthPrime(int i) { 26 | var task = new FutureTask<>(() -> { 27 | try { 28 | TimeUnit.SECONDS.sleep(100); 29 | return (long)Integer.MAX_VALUE; 30 | } catch (InterruptedException e) { 31 | System.err.println("Cancelling search"); 32 | throw new CancellationException("interrupted"); 33 | } 34 | }); 35 | new Thread(task).start(); 36 | return task; 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Ch06/ch06/RWLockExamples.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.concurrent.locks.ReentrantReadWriteLock; 4 | 5 | public class RWLockExamples { 6 | private final ReentrantReadWriteLock lock = new ReentrantReadWriteLock(); 7 | 8 | private void run() { 9 | lock.readLock(); 10 | } 11 | 12 | public static void main(String[] args) { 13 | var rwl = new RWLockExamples(); 14 | rwl.run(); 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ch06/ch06/TaskManager.java: -------------------------------------------------------------------------------- 1 | package ch06; 2 | 3 | import java.util.concurrent.atomic.AtomicBoolean; 4 | 5 | public class TaskManager implements Runnable { 6 | private final AtomicBoolean shutdown = new AtomicBoolean(false); 7 | 8 | public void shutdown() { 9 | shutdown.set(true); 10 | } 11 | 12 | @Override 13 | public void run() { 14 | while (!shutdown.get()) { 15 | // do some work - e.g. process a work unit 16 | } 17 | } 18 | } -------------------------------------------------------------------------------- /Ch06/ch06/accounts/Main.java: -------------------------------------------------------------------------------- 1 | package ch06.accounts; 2 | 3 | public class Main { 4 | 5 | public static void main(String[] args) throws InterruptedException { 6 | var manager = new AccountManager(); 7 | manager.init(); 8 | var acc1 = manager.createAccount(1000); 9 | var acc2 = manager.createAccount(20_000); 10 | 11 | var transfer = new TransferTask(acc1, acc2, 100); 12 | manager.submit(transfer); 13 | Thread.sleep(5000); 14 | System.out.println(acc1); 15 | System.out.println(acc2); 16 | manager.shutdown(); 17 | manager.await(); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch06/ch06/accounts/TransferTask.java: -------------------------------------------------------------------------------- 1 | package ch06.accounts; 2 | 3 | public class TransferTask { 4 | private final Account sender; 5 | private final Account receiver; 6 | private final int amount; 7 | 8 | public TransferTask(Account sender, Account receiver, int amount) { 9 | this.sender = sender; 10 | this.receiver = receiver; 11 | this.amount = amount; 12 | } 13 | 14 | public Account sender() { 15 | return sender; 16 | } 17 | 18 | public int amount() { 19 | return amount; 20 | } 21 | 22 | public Account receiver() { 23 | return receiver; 24 | } 25 | 26 | } 27 | -------------------------------------------------------------------------------- /Ch07/README.md: -------------------------------------------------------------------------------- 1 | # Ch07 - Understanding Java performanc 2 | 3 | ## `Ch07/ch07` 4 | 5 | This directory contains an example for understanding the interactions of our 6 | code's performance with the L1 cache. These example should be used with Java 11+. 7 | 8 | * `CacheTester.java` - Example running code in various ways to hit or miss the L1 cache. 9 | 10 | This example may be loaded and run in an IDE at the `Ch07` level. You may need 11 | to set the project JDK version. 12 | 13 | To compile and run these samples at the command-line, do the following: 14 | 15 | ``` 16 | cd Ch07/ch07 17 | javac *.java 18 | java -cp .. ch07.CacheTester 19 | ``` 20 | -------------------------------------------------------------------------------- /Ch07/ch07/CacheTester.java: -------------------------------------------------------------------------------- 1 | package ch07; 2 | 3 | public class CacheTester { 4 | private final int ARR_SIZE = 2 * 1024 * 1024; 5 | private final int[] arr = new int[ARR_SIZE]; 6 | 7 | private void doLoop2() { 8 | for (var i = 0; i < arr.length; i = i + 1) { 9 | arr[i]++; // #A 10 | } 11 | } 12 | 13 | private void doLoop1() { 14 | for (var i = 0; i < arr.length; i = i + 16) { 15 | arr[i]++; // #B 16 | } 17 | } 18 | 19 | private void run() { 20 | for (var i = 0; i < 10_000; i = i + 1) { // #C 21 | doLoop1(); 22 | doLoop2(); 23 | } 24 | 25 | for (var i = 0; i < 100; i = i + 1) { 26 | long t0 = System.nanoTime(); 27 | doLoop1(); 28 | long t1 = System.nanoTime(); 29 | doLoop2(); 30 | long t2 = System.nanoTime(); 31 | 32 | long el = t1 - t0; 33 | long el2 = t2 - t1; 34 | 35 | System.out.println("Loop1: "+ el +" nanos ; Loop2: "+ el2); 36 | } 37 | } 38 | 39 | public static void main(String[] args) { 40 | var ct = new CacheTester(); 41 | ct.run(); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Ch09/BasicFunctionDeclarations.kts: -------------------------------------------------------------------------------- 1 | // Basic function definition 2 | fun doTheThing(value: Int): Boolean { 3 | println("Done $value!") 4 | return true 5 | } 6 | 7 | doTheThing(42) 8 | 9 | // Single-line definition 10 | fun checkCondition(n: Int) = n == 42 11 | checkCondition(1) 12 | checkCondition(42) 13 | -------------------------------------------------------------------------------- /Ch09/Collections.kts: -------------------------------------------------------------------------------- 1 | // Creating collections 2 | val readOnlyList = listOf("a", "b", "c") 3 | val mutableList = mutableListOf("a", "b", "c") 4 | 5 | val readOnlyMap = mapOf("a" to 1, "b" to 2, "c" to 3) 6 | val mutableMap = mutableMapOf("a" to 1, "b" to 2, "c" to 3) 7 | 8 | val readOnlySet = setOf(0, 1, 2) 9 | val mutableMap = mutableSetOf(1, 2, 3) 10 | 11 | // Iterating, mapping and filtering 12 | val l = listOf("a", "b", "c") 13 | 14 | for (s in l) { 15 | println(s) 16 | } 17 | 18 | val mapped = l.map { it.toUpperCase() } 19 | println(mapped) 20 | 21 | val filtered = l.filter { it != "b" } 22 | println(filtered) 23 | 24 | val all = l.all { it.length == 1 } 25 | println(all) 26 | 27 | val any = l.any { it.length == 2 } 28 | println(any) 29 | 30 | val none = l.none { it == "a" } 31 | println(none) 32 | 33 | val resultWith = l.associateWith { it.length } 34 | println(resultWith) 35 | 36 | val resultBy = l.associateBy { it.length } 37 | println(resultBy) 38 | -------------------------------------------------------------------------------- /Ch09/Companions.kts: -------------------------------------------------------------------------------- 1 | class ShyObject private constructor(val name: String) { 2 | 3 | companion object { 4 | fun create(name: String): ShyObject { 5 | return ShyObject(name) 6 | } 7 | } 8 | } 9 | 10 | ShyObject.create("The Thing") 11 | -------------------------------------------------------------------------------- /Ch09/DataClasses.kts: -------------------------------------------------------------------------------- 1 | // Standard class, needs equality defined or reverts to reference 2 | class PlainPoint(val x: Int, val y: Int) 3 | 4 | val pl1 = PlainPoint(1, 1) 5 | val pl2 = PlainPoint(1, 1) 6 | 7 | println(pl1 == pl2) 8 | 9 | 10 | // Data class, brings along property level equality 11 | data class DataPoint(val x: Int, val y: Int) 12 | 13 | val pd1 = DataPoint(1, 1) 14 | val pd2 = DataPoint(1, 1) 15 | 16 | println(pd1 == pd2) 17 | -------------------------------------------------------------------------------- /Ch09/Expressions.kts: -------------------------------------------------------------------------------- 1 | // if's are expressions 2 | val iffy = if (1 == 1) { 3 | "sure" 4 | } else { 5 | "nope" 6 | } 7 | println(iffy) 8 | 9 | // one-liner - Kotlin's answer to the ternary ? : in most C-like languages 10 | val myTurn = if (1 == 1) "sure" else "nope" 11 | println(myTurn) 12 | 13 | // when - Kotlin's answer to case statements 14 | val x = 2 15 | val w = when (x) { 16 | 1 -> "one" 17 | 2 -> "two" 18 | else -> "lots" 19 | } 20 | 21 | // when + in for collection inclusion 22 | val valid = listOf(1, 2, 3) 23 | val invalid = listOf(4, 5, 6) 24 | 25 | val collected = when (x) { 26 | in valid -> "valid" 27 | in invalid -> "invalid" 28 | else -> "unknown" 29 | } 30 | 31 | // when + ranges 32 | val ranged = when (x) { 33 | in 1..3 -> "valid" 34 | in 4..6 -> "invalid" 35 | else -> "unknown" 36 | } 37 | 38 | // when + functions 39 | fun theBest() = 1 40 | fun okValues() = listOf(2, 3) 41 | 42 | val message = when (x) { 43 | theBest() -> "best!" 44 | in okValues() -> "ok!" 45 | else -> "nope" 46 | } 47 | 48 | // try/catch as expression 49 | fun dangerousCall(): Nothing = throw RuntimeException("boo") 50 | val errorMessage = try { 51 | dangerousCall() 52 | "fine" 53 | } catch (e: Exception) { 54 | "oops" 55 | } 56 | -------------------------------------------------------------------------------- /Ch09/FirstClassFunctions.kts: -------------------------------------------------------------------------------- 1 | // Capturing as variables 2 | fun checkCondition(n: Int) = n == 42 3 | val check = ::checkCondition 4 | check(42) 5 | 6 | // Lambda syntax 7 | val anotherCheck = { n: Int -> n > 100 } 8 | anotherCheck(1000) 9 | anotherCheck.invoke(1000) 10 | 11 | // Function that takes another function as an argument 12 | fun callsAnother(funky: (Int) -> Unit) { 13 | funky(42) 14 | } 15 | 16 | // Lambda with and without explicit type 17 | callsAnother({ n: Int -> println("Got $n") }) 18 | callsAnother({ n -> println("Got $n") }) 19 | 20 | // Single-argument lambdas `it` 21 | callsAnother({ println("Got $it") }) 22 | callsAnother() { println("Got $it") } 23 | callsAnother { println("Got $it") } 24 | -------------------------------------------------------------------------------- /Ch09/Interfaces.kts: -------------------------------------------------------------------------------- 1 | import java.time.LocalDate 2 | 3 | interface Greetable { 4 | fun greet(): String 5 | } 6 | 7 | open class Person constructor( 8 | val birthdate: LocalDate, 9 | var name: String): Greetable { 10 | 11 | override fun greet(): String { 12 | return "Hello there" 13 | } 14 | } 15 | 16 | val greet : Greetable = Person(LocalDate.of(1996, 1, 23), "Some Body") 17 | println(greet.greet()) 18 | -------------------------------------------------------------------------------- /Ch09/NamedArguments.kts: -------------------------------------------------------------------------------- 1 | // Named arguments 2 | fun coordinates(x: Int, y: Int) { 3 | println("($x, $y)") 4 | } 5 | 6 | // These will print the same 7 | coordinates(10, 20) 8 | coordinates(y = 20, x = 10) 9 | -------------------------------------------------------------------------------- /Ch09/Nullability.kts: -------------------------------------------------------------------------------- 1 | // Won't compile for your (null) safety! 2 | // val i: Int = null 3 | // val s: String = null 4 | 5 | val i: Int? = null 6 | val s: String? = null 7 | 8 | // Won't compile because of null safety checks 9 | // println(s.length) 10 | 11 | // Compiles, and returns null if `s` is null 12 | println(s?.length) 13 | 14 | // Compiles, but will raise exception at runtime 15 | // println(s!!.length) 16 | 17 | // Compiles happily thanks to smart casting 18 | if (s != null) { 19 | println(s.length) 20 | } 21 | 22 | data class Node(val parent: Node?, val value: String) 23 | 24 | // Chaining ?. 25 | val node = Node(null, "only one") 26 | println(node.parent?.parent?.parent) 27 | -------------------------------------------------------------------------------- /Ch09/Person.kts: -------------------------------------------------------------------------------- 1 | import java.time.LocalDate 2 | import java.time.LocalDateTime 3 | 4 | open class Person( 5 | val birthdate: LocalDate, 6 | var name: String) { 7 | 8 | // Secondary constructor 9 | constructor(name: String) 10 | : this(LocalDate.of(0, 1, 1), name) { 11 | } 12 | 13 | init { 14 | if (birthdate.year < 2000) { 15 | println("So last century") 16 | } 17 | // println(nameParts) // Not allowed because nameParts not initialized yet 18 | } 19 | 20 | val nameParts: List = name.split(" ") 21 | 22 | init { 23 | println(nameParts) 24 | } 25 | 26 | open fun isBirthday(): Boolean { 27 | return today() == birthdate 28 | } 29 | 30 | private fun today(): LocalDate { 31 | return LocalDateTime.now().toLocalDate() 32 | } 33 | } 34 | 35 | val person = Person(LocalDate.of(1996, 1, 23), "Somebody Else") 36 | println("Hi ${person.name}. You were born on ${person.birthdate}") 37 | 38 | val someoneElse = Person("Someone Else") 39 | println("Oh hi ${someoneElse.name}. You weren't really born ${someoneElse.birthdate}, were you?") 40 | 41 | // Subclass of Person 42 | class Child(birthdate: LocalDate, name: String) 43 | : Person(birthdate, name) { 44 | 45 | override fun isBirthday(): Boolean { 46 | val itsToday = super.isBirthday() 47 | if (itsToday) { 48 | println("YIPPY!!") 49 | } 50 | return itsToday 51 | } 52 | } 53 | 54 | val child = Child(LocalDateTime.now().toLocalDate(), "Born Today") 55 | child.isBirthday() 56 | -------------------------------------------------------------------------------- /Ch09/PersonWithProperties.kts: -------------------------------------------------------------------------------- 1 | import java.time.LocalDate 2 | 3 | class Person { 4 | val birthdate = LocalDate.of(1996, 1, 23) 5 | var name = "Name Here" 6 | } 7 | 8 | val person = Person() 9 | println("Hi ${person.name}. You were born on ${person.birthdate}") 10 | person.name = "Somebody Else" 11 | 12 | // Fails to compile because val doesn't create a setter 13 | // person.birthdate = LocalDate.of(2000, 1, 1) 14 | -------------------------------------------------------------------------------- /Ch09/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 9 - Kotlin 2 | 3 | ## `Ch09/*.kts` 4 | 5 | This directory contains Kotlin script files (`*.kts`) demonstrating most of the 6 | snippets of Kotlin shown through the chapter. 7 | 8 | These snippets may be loaded in an IDE and run individually. They may be run at 9 | the command-line directly as follows: 10 | 11 | ``` 12 | kotlin Expressions.kts 13 | ``` 14 | 15 | # `Ch09/coroutine-app` and `Ch09/interoperability` 16 | 17 | These directories each contain a full example application in Kotlin. 18 | 19 | * `Ch09/coroutine-app` - Basic co-routines usage 20 | * `Ch09/interoperatbility` - Kotlin + Java interoperatbility 21 | 22 | The projects use Gradle and may be loaded in an IDE or built on the 23 | command-line: 24 | 25 | ``` 26 | ./gradlew run 27 | ``` 28 | -------------------------------------------------------------------------------- /Ch09/SmartCasting.kts: -------------------------------------------------------------------------------- 1 | // Smart casting knows 2 | val maybeNullString: String? = null 3 | if (maybeNullString != null) { 4 | println(maybeNullString.length) 5 | } 6 | 7 | // Smart casting knows when we're a string 8 | val anyString: Any = "Hello" 9 | if (anyString is String) { 10 | println(anyString.toUpperCase()) 11 | } 12 | 13 | // Smart casting works within the condition too 14 | if (anyString is String && anyString.toUpperCase() == "HELLO") { 15 | println("Got something") 16 | } 17 | 18 | // Mutable properties (among other cases) can't use smart casting 19 | class CantSmartCast(var str: Any = "boo") { 20 | fun checkIt() { 21 | if (str is String) { 22 | // Disallowed because `str` is a mutable property 23 | //println(str.toUpperCase()) 24 | 25 | // We can still cast it by hand though 26 | val cast = str as String 27 | println(cast.toUpperCase()) 28 | } 29 | } 30 | } 31 | 32 | CantSmartCast().checkIt() 33 | -------------------------------------------------------------------------------- /Ch09/TypeDeclarationsAndEquality.kts: -------------------------------------------------------------------------------- 1 | // Inferred types 2 | var i1: Int = 1 3 | var s1: String = "String" 4 | 5 | // Declaring types 6 | var i2: Int = 1 7 | var s2: String = "String" 8 | //var n: Int = "error" // Won't compile because of type mismatch 9 | 10 | // More natural equality 11 | var s: String = "A " + "value" 12 | if (s == "A value") { 13 | println("They match!") 14 | } 15 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/README.md: -------------------------------------------------------------------------------- 1 | # Kotlin Co-routine samples for Chapter 8 of Well-Grounded Java Developer 2 | 3 | Each of the `*Main.kt` files represents a separate example you can run. 4 | 5 | ``` 6 | ./gradlew run # Main.kt 7 | ./gradlew runCancel # CancellingMain.kt 8 | ``` 9 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "com.wellgrounded" 2 | version = "0.1.0" 3 | 4 | plugins { 5 | kotlin("jvm") version "1.6.10" 6 | application 7 | } 8 | 9 | application { 10 | mainClass.set("com.wellgrounded.kotlin.MainKt") 11 | } 12 | 13 | task("runCancel", JavaExec::class) { 14 | mainClass.set("com.wellgrounded.kotlin.CancellingMainKt") 15 | classpath = sourceSets["main"].runtimeClasspath 16 | } 17 | 18 | task("runCoop", JavaExec::class) { 19 | mainClass.set("com.wellgrounded.kotlin.CoopMainKt") 20 | classpath = sourceSets["main"].runtimeClasspath 21 | } 22 | 23 | tasks.jar { 24 | manifest { 25 | attributes("Main-Class" to application.mainClass) 26 | } 27 | } 28 | 29 | repositories { 30 | mavenCentral() 31 | mavenLocal() 32 | } 33 | 34 | dependencies { 35 | implementation(kotlin("stdlib")) 36 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.3.9") 37 | } 38 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch09/coroutines-app/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch09/coroutines-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "coroutines-app" 2 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/src/main/kotlin/CancellingMain.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.delay 5 | import kotlinx.coroutines.launch 6 | 7 | fun main() { 8 | // Capture the coroutine object returned by `launch` 9 | val co = GlobalScope.launch { 10 | // Inside of coroutines we can call `delay` to wait a period of time 11 | delay(1000) 12 | println("Inside!") 13 | } 14 | // Cancel the coroutine immediately 15 | co.cancel() 16 | 17 | println("Outside and we cancelled Inside") 18 | 19 | // Wait as long as you like here, you'll never see the coroutine output 20 | Thread.sleep(2000) 21 | } 22 | 23 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/src/main/kotlin/CoopMain.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.coroutineScope 5 | import kotlinx.coroutines.delay 6 | import kotlinx.coroutines.launch 7 | 8 | fun main() { 9 | // Start our parent coroutine as before 10 | val co = GlobalScope.launch { 11 | // Start two child coroutines. `coroutineScope` associates those to the 12 | // enclosing scope - in this case our global coroutine. 13 | coroutineScope { 14 | delay(1000) 15 | println("First") 16 | } 17 | coroutineScope { 18 | delay(1000) 19 | println("Second") 20 | } 21 | } 22 | 23 | // Cancel the parent coroutine 24 | co.cancel() 25 | 26 | // Again, we can wait here but we won't see any output 27 | println("Only this output because all the children cancelled too") 28 | Thread.sleep(4000) 29 | } 30 | -------------------------------------------------------------------------------- /Ch09/coroutines-app/src/main/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin 2 | 3 | import kotlinx.coroutines.GlobalScope 4 | import kotlinx.coroutines.launch 5 | 6 | fun main() { 7 | GlobalScope.launch { 8 | println("Inside!") 9 | } 10 | println("Outside") 11 | 12 | // If we exclude this sleep, most often you'll only see Outside as the 13 | // coroutine doesn't stop the app from exiting when done! 14 | Thread.sleep(1000) 15 | } 16 | -------------------------------------------------------------------------------- /Ch09/interoperability/README.md: -------------------------------------------------------------------------------- 1 | # Small app to show Kotlin-Java interoperability features 2 | 3 | `./gradlew build` will generate the class files for inspection under `./build` 4 | directory. 5 | 6 | To run the sample at the command-line, use `./gradlew run` 7 | -------------------------------------------------------------------------------- /Ch09/interoperability/build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "com.wellgrounded" 2 | version = "0.1.0" 3 | 4 | plugins { 5 | kotlin("jvm") version "1.6.10" 6 | application 7 | } 8 | 9 | application { 10 | mainClass.set("com.wellgrounded.kotlin.Main") 11 | } 12 | 13 | tasks.jar { 14 | manifest { 15 | attributes("Main-Class" to application.mainClass) 16 | } 17 | } 18 | 19 | tasks.withType { 20 | kotlinOptions { 21 | jvmTarget = "11" 22 | } 23 | } 24 | 25 | repositories { 26 | mavenCentral() 27 | mavenLocal() 28 | } 29 | 30 | dependencies { 31 | implementation(kotlin("stdlib")) 32 | } 33 | -------------------------------------------------------------------------------- /Ch09/interoperability/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch09/interoperability/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch09/interoperability/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch09/interoperability/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "coroutines-app" 2 | -------------------------------------------------------------------------------- /Ch09/interoperability/src/main/java/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin; 2 | 3 | import com.wellgrounded.kotlin.MainKt; 4 | import com.wellgrounded.kotlin.Person; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | // Calling a top-level function in Kotlin 9 | MainKt.shout(); 10 | 11 | // Using a Kotlin-defined class 12 | Person p = new Person("Some Body"); 13 | System.out.println(p.getName()); 14 | 15 | p.setName("Somebody Else"); 16 | System.out.println(p.getName()); 17 | 18 | // Kotlin's default values 19 | p.greet(); 20 | p.greet("Howdy"); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Ch09/interoperability/src/main/kotlin/Main.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin 2 | 3 | fun shout() { 4 | println("No classes in sight!") 5 | } 6 | -------------------------------------------------------------------------------- /Ch09/interoperability/src/main/kotlin/Person.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin 2 | 3 | class Person(var name: String) { 4 | // Annotating with JvmOverloads allows Java to use our default values too 5 | @JvmOverloads 6 | fun greet(words: String = "Hi there") { 7 | println(words) 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch10/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 10 - Clojure 2 | 3 | ## `Ch10` 4 | 5 | This directory contains a Java implementation of several concepts from 6 | Clojure to in Java to help you better understand how Clojure works. 7 | 8 | This may be loaded in an IDE at the `Ch10` level. It requires Java 17+ to run. 9 | 10 | They may be run at the command-line directly as follows: 11 | 12 | ``` 13 | ch Ch10 14 | javac examples/*.java 15 | javac lang/*.java 16 | java examples.Main 17 | ``` 18 | -------------------------------------------------------------------------------- /Ch10/examples/EmptyIterator.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import java.util.Iterator; 4 | 5 | public class EmptyIterator implements Iterator { 6 | @Override 7 | public boolean hasNext() { 8 | return false; 9 | } 10 | 11 | @Override 12 | public Object next() { 13 | throw new IllegalStateException("Can't advance an empty iterator"); 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ch10/examples/IntGeneratorSeq.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import lang.ISeq; 4 | 5 | import java.util.function.Function; 6 | 7 | public class IntGeneratorSeq implements ISeq { 8 | private final int current; 9 | private final Function generator; 10 | 11 | private IntGeneratorSeq(int current, Function generator) { 12 | this.current = current; 13 | this.generator = generator; 14 | } 15 | 16 | public static IntGeneratorSeq of(int start, Function generator) { 17 | return new IntGeneratorSeq(start, generator); 18 | } 19 | 20 | @Override 21 | public Object first() { 22 | return generator.apply(current); 23 | } 24 | 25 | @Override 26 | public ISeq rest() { 27 | return new IntGeneratorSeq(generator.apply(current), generator); 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ch10/examples/SeqExamples.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import lang.ArraySeq; 4 | import lang.ISeq; 5 | 6 | import java.util.List; 7 | 8 | public class SeqExamples { 9 | 10 | public static void main(String[] args) { 11 | ISeq seq = ArraySeq.of(List.of(10000,20000,30000)); 12 | var o1 = seq.first(); 13 | var o2 = seq.first(); 14 | System.out.println(o1 == o2); 15 | 16 | while (seq.first() != null) { 17 | System.out.println(seq.first()); 18 | seq = seq.rest(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ch10/examples/SquareSeq.java: -------------------------------------------------------------------------------- 1 | package examples; 2 | 3 | import lang.ISeq; 4 | 5 | public class SquareSeq implements ISeq { 6 | private final int current; 7 | 8 | private SquareSeq(int current) { 9 | this.current = current; 10 | } 11 | 12 | public static SquareSeq of(int start) { 13 | if (start < 0) { 14 | return new SquareSeq(-start); 15 | } 16 | return new SquareSeq(start); 17 | } 18 | 19 | @Override 20 | public Object first() { 21 | return Integer.valueOf(current * current); 22 | } 23 | 24 | @Override 25 | public ISeq rest() { 26 | return new SquareSeq(current + 1); 27 | } 28 | } 29 | -------------------------------------------------------------------------------- /Ch10/lang/ArraySeq.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | import java.util.List; 4 | 5 | public class ArraySeq implements ISeq { 6 | private final int index; 7 | private final Object[] values; 8 | 9 | private ArraySeq(int index, Object[] values) { 10 | this.index = index; 11 | this.values = values; 12 | } 13 | 14 | public static ArraySeq of(List objects) { 15 | if (objects == null || objects.size() == 0) { 16 | return Empty.of(); 17 | } 18 | return new ArraySeq(0, objects.toArray()); 19 | } 20 | 21 | @Override 22 | public Object first() { 23 | return values[index]; 24 | } 25 | 26 | @Override 27 | public ISeq rest() { 28 | if (index >= values.length - 1) { 29 | return Empty.of(); 30 | } 31 | return new ArraySeq(index + 1, values); 32 | } 33 | 34 | public int count() { 35 | return values.length - index; 36 | } 37 | 38 | 39 | public static class Empty extends ArraySeq { 40 | private static Empty EMPTY = new Empty(0, new Object[0]); 41 | 42 | private Empty(int index, Object[] values) { 43 | super(index, values); 44 | } 45 | 46 | public static Empty of() { 47 | return EMPTY; 48 | } 49 | 50 | @Override 51 | public Object first() { 52 | return null; 53 | } 54 | 55 | @Override 56 | public ISeq rest() { 57 | return of(); 58 | } 59 | 60 | public int count() { 61 | return 0; 62 | } 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /Ch10/lang/IFn.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | public interface IFn { 4 | default Object invoke() { 5 | return throwArity(); 6 | } 7 | default Object invoke(Object o1) { 8 | return throwArity(); 9 | } 10 | default Object invoke(Object o1, Object o2) { 11 | return throwArity(); 12 | } 13 | default Object invoke(Object o1, Object o2, Object o3) { 14 | return throwArity(); 15 | } 16 | default Object invoke(Object o1, Object o2, Object... others) { 17 | return throwArity(); 18 | } 19 | 20 | default Object throwArity(){ 21 | throw new IllegalArgumentException("Wrong number of args passed to function: " 22 | + toString()); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch10/lang/ISeq.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | public interface ISeq { 4 | Object first(); 5 | ISeq rest(); 6 | } 7 | -------------------------------------------------------------------------------- /Ch10/lang/Keyword.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | import java.lang.ref.Reference; 4 | import java.lang.ref.WeakReference; 5 | import java.util.Map; 6 | import java.util.concurrent.ConcurrentHashMap; 7 | 8 | public class Keyword implements IFn { 9 | private static Map table = new ConcurrentHashMap<>(); 10 | private final Symbol sym; 11 | 12 | private Keyword(Symbol sym) { 13 | this.sym = sym; 14 | } 15 | 16 | public static Keyword of(Symbol sym) { 17 | var existing = table.get(sym); 18 | if (existing == null) { 19 | var k = new Keyword(sym); 20 | table.put(sym, k); 21 | return k; 22 | } 23 | return existing; 24 | } 25 | 26 | @Override 27 | public String toString() { 28 | return ":" + sym; 29 | } 30 | 31 | @Override 32 | public Object invoke(Object shouldBeMap) { 33 | if (shouldBeMap instanceof Map map) { 34 | return map.get(this); 35 | } 36 | throw new IllegalArgumentException("Expected a map, got: "+ shouldBeMap); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Ch10/lang/MapBase.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | import java.util.*; 4 | 5 | public class MapBase extends AbstractMap implements IFn, Map { 6 | 7 | @Override 8 | public Object invoke(Object o) { 9 | return get(o); 10 | } 11 | 12 | // Dummy implementation - requires entrySet() to use AbstractMap as a base 13 | private transient Set entrySet; 14 | 15 | @Override 16 | public Set entrySet() { 17 | Set es; 18 | return (es = entrySet) == null ? (entrySet = new EntrySet()) : es; 19 | } 20 | 21 | final class EntrySet extends AbstractSet { 22 | @Override 23 | public Iterator iterator() { 24 | return null; 25 | } 26 | 27 | @Override 28 | public int size() { 29 | return 0; 30 | } 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Ch10/lang/Namespace.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | import java.util.Map; 4 | import java.util.concurrent.ConcurrentHashMap; 5 | 6 | public class Namespace { 7 | private final Map bindings = new ConcurrentHashMap<>(); 8 | 9 | private static final Map namespaces = new ConcurrentHashMap<>(); 10 | 11 | private final Symbol ns; 12 | 13 | private Namespace(Symbol ns) { 14 | this.ns = ns; 15 | } 16 | 17 | public static Namespace of(Symbol ns) { 18 | return new Namespace(ns); 19 | } 20 | 21 | public static Namespace findOrCreate(Symbol name){ 22 | Namespace ns = namespaces.get(name); 23 | if(ns != null) 24 | return ns; 25 | Namespace newns = new Namespace(name); 26 | ns = namespaces.putIfAbsent(name, newns); 27 | return ns == null ? newns : ns; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ch10/lang/Symbol.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | public class Symbol { 4 | final String ns; 5 | final String name; 6 | transient String _str; 7 | 8 | private Symbol(String ns, String name) { 9 | this.ns = ns; 10 | this.name = name; 11 | } 12 | 13 | public static Symbol of(String ns, String name){ 14 | return new Symbol(ns, name); 15 | } 16 | 17 | public static Symbol of(String nsname){ 18 | int i = nsname.indexOf('/'); 19 | if(i == -1 || nsname.equals("/")) 20 | return new Symbol(null, nsname); 21 | else 22 | return new Symbol(nsname.substring(0, i), nsname.substring(i + 1)); 23 | } 24 | 25 | public String toString(){ 26 | if (_str == null) { 27 | if (ns != null) { 28 | _str = (ns + "/" + name); 29 | } else { 30 | _str = name; 31 | } 32 | } 33 | return _str; 34 | } 35 | 36 | } 37 | -------------------------------------------------------------------------------- /Ch10/lang/Utils.java: -------------------------------------------------------------------------------- 1 | package lang; 2 | 3 | import java.util.concurrent.Callable; 4 | import java.util.function.Function; 5 | 6 | public class Utils { 7 | private Utils() { } 8 | 9 | public static IFn makeFn0(Callable c) { 10 | return new IFn() { 11 | @Override 12 | public Object invoke() { 13 | try { 14 | return c.call(); 15 | } catch (Exception e) { 16 | throw new RuntimeException(e); 17 | } 18 | } 19 | }; 20 | } 21 | 22 | public static IFn makeFn1(Function f) { 23 | return new IFn() { 24 | @Override 25 | public Object invoke(Object o1) { 26 | return f.apply(o1); 27 | } 28 | }; 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-11/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | } 4 | 5 | application { 6 | mainClass.set("com.wellgrounded.Main") 7 | } 8 | 9 | tasks.jar { 10 | manifest { 11 | attributes("Main-Class" to application.mainClass) 12 | } 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | implementation("com.sun.activation:jakarta.activation:1.2.2") 21 | implementation("org.glassfish.corba:glassfish-corba-omgapi:4.2.1") 22 | implementation("javax.transaction:javax.transaction-api:1.3") 23 | implementation("jakarta.xml.bind:jakarta.xml.bind-api:2.3.3") 24 | implementation("jakarta.xml.ws:jakarta.xml.ws-api:2.3.3") 25 | implementation("jakarta.annotation:jakarta.annotation-api:1.3.5") 26 | } 27 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-11/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-ee-11/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-ee-11/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-11/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-ee-11" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-11/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import javax.activation.CommandInfo; 4 | import org.omg.CORBA.ORB; 5 | import javax.transaction.TransactionRequiredException; 6 | import javax.xml.bind.JAXB; 7 | import javax.xml.ws.Action; 8 | import javax.annotation.Resource; 9 | 10 | public class Main { 11 | public static void main(String[] args) { 12 | System.out.println(CommandInfo.class.getName()); 13 | System.out.println(ORB.class.getName()); 14 | System.out.println(TransactionRequiredException.class.getName()); 15 | System.out.println(JAXB.class.getName()); 16 | System.out.println(Action.class.getName()); 17 | System.out.println(Resource.class.getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-8/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | } 4 | 5 | application { 6 | mainClass.set("com.wellgrounded.Main") 7 | } 8 | 9 | tasks.jar { 10 | manifest { 11 | attributes("Main-Class" to application.mainClass) 12 | } 13 | } 14 | 15 | repositories { 16 | mavenCentral() 17 | } 18 | 19 | dependencies { 20 | } 21 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-8/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-ee-8/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-ee-8/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-8/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-ee-8" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-ee-8/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import javax.activation.CommandInfo; 4 | import org.omg.CORBA.ORB; 5 | import javax.transaction.TransactionRequiredException; 6 | import javax.xml.bind.JAXB; 7 | import javax.xml.ws.Action; 8 | import javax.annotation.Resource; 9 | 10 | public class Main { 11 | public static void main(String[] args) { 12 | System.out.println(CommandInfo.class.getName()); 13 | System.out.println(ORB.class.getName()); 14 | System.out.println(TransactionRequiredException.class.getName()); 15 | System.out.println(JAXB.class.getName()); 16 | System.out.println(Action.class.getName()); 17 | System.out.println(Resource.class.getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch11/gradle-example/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | id("org.jetbrains.kotlin.jvm") version "1.4.31" 4 | id("com.github.spotbugs") version "4.3.0" 5 | } 6 | 7 | application { 8 | mainClass.set("com.wellgrounded.Main") 9 | } 10 | 11 | tasks.jar { 12 | manifest { 13 | attributes("Main-Class" to application.mainClass) 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") 23 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") 24 | } 25 | 26 | tasks.named("test") { 27 | useJUnitPlatform() 28 | } 29 | 30 | tasks.withType().configureEach { 31 | reports.create("html") { 32 | isEnabled = true 33 | setStylesheet("fancy-hist.xsl") 34 | } 35 | } 36 | 37 | open class YoPluginExtension { 38 | var count: Int = 1 39 | } 40 | 41 | class YoPlugin : Plugin { 42 | override fun apply(project: Project) { 43 | val extension = project.extensions.create("yo") 44 | project.task("yo") { 45 | doLast { 46 | repeat(extension.count) { 47 | println("yo") 48 | } 49 | } 50 | } 51 | } 52 | } 53 | 54 | apply() 55 | 56 | configure { 57 | count = 4 58 | } 59 | -------------------------------------------------------------------------------- /Ch11/gradle-example/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-example/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-example/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-example/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-example" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-example/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import com.wellgrounded.kotlin.MessageFromKotlin; 4 | 5 | public class Main { 6 | public static String getMessage() { 7 | return "Gradle for fun and profit"; 8 | } 9 | 10 | public static void main(String[] args) { 11 | System.out.println(MessageFromKotlin.getMessage()); 12 | System.out.println(getMessage()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch11/gradle-example/src/main/kotlin/com/wellgrounded/kotlin/MessageFromKotlin.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.kotlin 2 | 3 | class MessageFromKotlin { 4 | companion object { 5 | @JvmStatic 6 | fun getMessage() : String { 7 | return "Howdy from Kotlin!" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch11/gradle-example/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | 7 | public class MainTest { 8 | @Test 9 | public void checkMessage() { 10 | var unused = ""; 11 | assertNotNull(Main.getMessage()); 12 | } 13 | 14 | // Uncomment to cause a SpotBugs failure 15 | //public boolean equals(Object anObject) { 16 | //return true; 17 | //} 18 | } 19 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.beryx.jlink") version("2.23.3") 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | application { 10 | mainModule.set("wgjd.discovery") 11 | mainClass.set("wgjd.discovery.Discovery") 12 | } 13 | 14 | java { 15 | modularity.inferModulePath.set(true) 16 | } 17 | 18 | sourceSets { 19 | main { 20 | java { 21 | setSrcDirs(listOf("src/wgjd.discovery")) 22 | } 23 | } 24 | } 25 | 26 | tasks.withType { 27 | options.compilerArgs = listOf("--add-exports", "jdk.internal.jvmstat/sun.jvmstat.monitor=wgjd.discovery") 28 | } 29 | 30 | tasks.jar { 31 | manifest { 32 | attributes("Main-Class" to application.mainClass) 33 | } 34 | } 35 | 36 | jlink { 37 | targetPlatform("local", System.getProperty("java.home")) 38 | //targetPlatform("linux-x64", "/Users/jasonclark/Downloads/linux_jdk-11.0.10+9") 39 | 40 | launcher{ 41 | jvmArgs = listOf("--add-exports", "jdk.internal.jvmstat/sun.jvmstat.monitor=wgjd.discovery") 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-jlink/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-jlink/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-jlink" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/src/wgjd.discovery/module-info.java: -------------------------------------------------------------------------------- 1 | module wgjd.discovery { 2 | exports wgjd.discovery; 3 | 4 | requires java.instrument; 5 | requires java.logging; 6 | requires jdk.attach; 7 | requires jdk.internal.jvmstat; 8 | } 9 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/src/wgjd.discovery/wgjd/discovery/Discovery.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery; 2 | 3 | import com.sun.tools.attach.VirtualMachine; 4 | import com.sun.tools.attach.VirtualMachineDescriptor; 5 | 6 | import java.util.List; 7 | import java.util.Set; 8 | 9 | import wgjd.discovery.internal.PlainAttachOutput; 10 | 11 | public class Discovery { 12 | private static final Set PROCESS_SKIP_TERMS = 13 | Set.of("sun.tools.jconsole.JConsole", "gradle"); 14 | 15 | public static void main(String[] args) { 16 | var output = new PlainAttachOutput(); 17 | 18 | try { 19 | final var vmConsumer = new VMIntrospector(); 20 | System.out.println("Java processes:"); 21 | System.out.println("PID\tDisplay Name\tVM Version\tAttachable"); 22 | 23 | final List vmds = VirtualMachine.list(); 24 | for (var vmd : vmds) { 25 | if (!skip(vmd)) { 26 | vmConsumer.accept(vmd); 27 | } 28 | } 29 | } finally { 30 | output.finished(); 31 | } 32 | } 33 | 34 | private static boolean skip(VirtualMachineDescriptor vmd) { 35 | final var displayName = vmd.displayName(); 36 | for (var term : PROCESS_SKIP_TERMS) { 37 | if (displayName.contains(term)) { 38 | return true; 39 | } 40 | } 41 | return // REVIEW not sure if we can skip all of these 42 | displayName.isEmpty(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/src/wgjd.discovery/wgjd/discovery/VMIntrospector.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery; 2 | 3 | import com.sun.tools.attach.VirtualMachineDescriptor; 4 | import sun.jvmstat.monitor.MonitorException; 5 | import sun.jvmstat.monitor.MonitoredHost; 6 | import sun.jvmstat.monitor.MonitoredVmUtil; 7 | import sun.jvmstat.monitor.VmIdentifier; 8 | 9 | import java.net.URISyntaxException; 10 | import java.util.function.Consumer; 11 | 12 | public class VMIntrospector implements Consumer { 13 | 14 | @Override 15 | public void accept(VirtualMachineDescriptor vmd) { 16 | var isAttachable = false; 17 | var vmVersion = ""; 18 | try { 19 | var vmId = new VmIdentifier(vmd.id()); 20 | var monitoredHost = MonitoredHost.getMonitoredHost(vmId); 21 | var monitoredVm = monitoredHost.getMonitoredVm(vmId, -1); 22 | try { 23 | isAttachable = MonitoredVmUtil.isAttachable(monitoredVm); 24 | vmVersion = MonitoredVmUtil.vmVersion(monitoredVm); 25 | } finally { 26 | monitoredHost.detach(monitoredVm); 27 | } 28 | } catch (URISyntaxException | MonitorException e) { 29 | e.printStackTrace(); 30 | } 31 | 32 | System.out.println( 33 | vmd.id() + '\t' + vmd.displayName() + '\t' + vmVersion + '\t' + isAttachable); 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/src/wgjd.discovery/wgjd/discovery/internal/AttachOutput.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery.internal; 2 | 3 | public interface AttachOutput { 4 | void attachStarted(String pid, String command, String agentArgs); 5 | 6 | void attachFinished(); 7 | 8 | void finished(); 9 | 10 | void error(Exception e); 11 | 12 | void warn(String message); 13 | } 14 | -------------------------------------------------------------------------------- /Ch11/gradle-jlink/src/wgjd.discovery/wgjd/discovery/internal/PlainAttachOutput.java: -------------------------------------------------------------------------------- 1 | package wgjd.discovery.internal; 2 | 3 | import java.io.PrintStream; 4 | 5 | public class PlainAttachOutput implements AttachOutput { 6 | private final PrintStream out; 7 | private final PrintStream err; 8 | 9 | public PlainAttachOutput() { 10 | this(System.out, System.err); 11 | } 12 | 13 | public PlainAttachOutput(PrintStream out, PrintStream err) { 14 | this.out = out; 15 | this.err = err; 16 | } 17 | 18 | @Override 19 | public void attachStarted(String pid, String command, String agentArgs) { 20 | out.println("\n--- Attaching ---"); 21 | out.println("\tPID "+ pid); 22 | out.println("\tCommand "+ command); 23 | } 24 | 25 | @Override 26 | public void finished() { 27 | out.flush(); 28 | err.flush(); 29 | } 30 | 31 | @Override 32 | public void attachFinished() {} 33 | 34 | @Override 35 | public void error(Exception e) { 36 | e.printStackTrace(err); 37 | } 38 | 39 | @Override 40 | public void warn(String message) {} 41 | } 42 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | } 4 | 5 | application { 6 | mainClass.set("com.wellgrounded.modapp.Main") 7 | mainModule.set("com.wellgrounded.modapp") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenLocal() 18 | mavenCentral() 19 | } 20 | 21 | java { 22 | modularity.inferModulePath.set(true) 23 | } 24 | 25 | sourceSets { 26 | main { 27 | java { 28 | setSrcDirs(listOf("src/com.wellgrounded.modapp/java")) 29 | } 30 | } 31 | } 32 | 33 | dependencies { 34 | implementation(files("../mod-lib/build/libs/gradle-mod-lib.jar")) 35 | } 36 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-app/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-modules/mod-app/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-app/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-mod-app" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-app/src/com.wellgrounded.modapp/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.modapp; 2 | 3 | import com.wellgrounded.modlib.visible.UseThis; 4 | //import com.wellgrounded.modlib.hidden.CantTouchThis; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | System.out.println(UseThis.getMessage()); 9 | //System.out.println(CantTouchThis.getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-app/src/com.wellgrounded.modapp/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.wellgrounded.modapp { 2 | requires com.wellgrounded.modlib; 3 | } 4 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | `java-library` 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | sourceSets { 10 | main { 11 | java { 12 | setSrcDirs(listOf("src/com.wellgrounded.modlib/java")) 13 | } 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-modules/mod-lib/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-mod-lib" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/src/com.wellgrounded.modlib/java/com/wellgrounded/hidden/CantTouchThis.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.modlib.hidden; 2 | 3 | public class CantTouchThis { 4 | public static String getMessage() { 5 | return "Hammertime"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/src/com.wellgrounded.modlib/java/com/wellgrounded/visible/UseThis.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.modlib.visible; 2 | 3 | public class UseThis { 4 | public static String getMessage() { 5 | return "Modules for fun and profit"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/mod-lib/src/com.wellgrounded.modlib/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.wellgrounded.modlib { 2 | exports com.wellgrounded.modlib.visible; 3 | } 4 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/non-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | } 4 | 5 | application { 6 | mainClass.set("com.wellgrounded.nonapp.Main") 7 | mainModule.set("com.wellgrounded.nonapp") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenLocal() 18 | mavenCentral() 19 | } 20 | 21 | sourceSets { 22 | main { 23 | java { 24 | setSrcDirs(listOf("src/com.wellgrounded.nonapp/java")) 25 | } 26 | } 27 | } 28 | 29 | dependencies { 30 | implementation(files("../mod-lib/build/libs/gradle-mod-lib.jar")) 31 | } 32 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/non-app/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch11/gradle-modules/non-app/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch11/gradle-modules/non-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/non-app/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "gradle-non-app" 11 | -------------------------------------------------------------------------------- /Ch11/gradle-modules/non-app/src/com.wellgrounded.nonapp/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.nonapp; 2 | 3 | import com.wellgrounded.modlib.visible.UseThis; 4 | import com.wellgrounded.modlib.hidden.CantTouchThis; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | System.out.println(UseThis.getMessage()); 9 | System.out.println(CantTouchThis.getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch11/maven-ee-11/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import javax.activation.CommandInfo; 4 | import org.omg.CORBA.ORB; 5 | import javax.transaction.TransactionRequiredException; 6 | import javax.xml.bind.JAXB; 7 | import javax.xml.ws.Action; 8 | import javax.annotation.Resource; 9 | 10 | public class Main { 11 | public static void main(String[] args) { 12 | System.out.println(CommandInfo.class.getName()); 13 | System.out.println(ORB.class.getName()); 14 | System.out.println(TransactionRequiredException.class.getName()); 15 | System.out.println(JAXB.class.getName()); 16 | System.out.println(Action.class.getName()); 17 | System.out.println(Resource.class.getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch11/maven-ee-8/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.wellgrounded 4 | example 5 | 1.0-SNAPSHOT 6 | maven-ee-8 7 | 8 | 9 | UTF-8 10 | 8 11 | 8 12 | 13 | 14 | 15 | 16 | 17 | org.apache.maven.plugins 18 | maven-jar-plugin 19 | 2.4 20 | 21 | 22 | 23 | true 24 | com.wellgrounded.Main 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | junit 35 | junit 36 | 4.13.2 37 | test 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Ch11/maven-ee-8/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import javax.activation.CommandInfo; 4 | import org.omg.CORBA.ORB; 5 | import javax.transaction.TransactionRequiredException; 6 | import javax.xml.bind.JAXB; 7 | import javax.xml.ws.Action; 8 | import javax.annotation.Resource; 9 | 10 | public class Main { 11 | public static void main(String[] args) { 12 | System.out.println(CommandInfo.class.getName()); 13 | System.out.println(ORB.class.getName()); 14 | System.out.println(TransactionRequiredException.class.getName()); 15 | System.out.println(JAXB.class.getName()); 16 | System.out.println(Action.class.getName()); 17 | System.out.println(Resource.class.getName()); 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /Ch11/maven-example/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import com.wellgrounded.MessageFromKotlin; 4 | 5 | public class Main { 6 | public static String getMessage() { 7 | return "Maven for fun and profit"; 8 | } 9 | 10 | public static void main(String[] args) { 11 | System.out.println(MessageFromKotlin.getMessage()); 12 | System.out.println(getMessage()); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch11/maven-example/src/main/kotlin/com/wellgrounded/MessageFromKotlin.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | class MessageFromKotlin { 4 | companion object { 5 | @JvmStatic 6 | fun getMessage() : String { 7 | return "Howdy from Kotlin!" 8 | } 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch11/maven-example/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.util.List; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertNotNull; 8 | 9 | public class MainTest { 10 | @Test 11 | public void checkMessage() { 12 | //var matcher = new StringContainsInOrder(List.of("boo")); 13 | //System.out.println("Hamcrest: " + matcher.getClass().getPackage().getImplementationVersion().toString()); 14 | var unused = ""; 15 | assertNotNull(Main.getMessage()); 16 | } 17 | 18 | public boolean equals(Object anObject) { 19 | return true; 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ch11/maven-example/src/test/java/com/wellgrounded/integration/LongRunningIT.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | 7 | public class LongRunningIT { 8 | @Test 9 | public void checkMessage() { 10 | var unused = ""; 11 | assertNotNull(Main.getMessage()); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ch11/maven-modules/mod-app/src/com.wellgrounded.modapp/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.modapp; 2 | 3 | import com.wellgrounded.modlib.visible.UseThis; 4 | //import com.wellgrounded.modlib.hidden.CantTouchThis; 5 | 6 | public class Main { 7 | public static void main(String[] args) { 8 | System.out.println(UseThis.getMessage()); 9 | //System.out.println(CantTouchThis.getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch11/maven-modules/mod-app/src/com.wellgrounded.modapp/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.wellgrounded.modapp { 2 | requires com.wellgrounded.modlib; 3 | } 4 | -------------------------------------------------------------------------------- /Ch11/maven-modules/mod-lib/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.wellgrounded 4 | modlib 5 | 2.0 6 | mod-lib 7 | 8 | 9 | UTF-8 10 | 11 11 | 11 12 | 13 | 14 | 15 | src/com.wellgrounded.modlib/java 16 | 17 | 18 | -------------------------------------------------------------------------------- /Ch11/maven-modules/mod-lib/src/com.wellgrounded.modlib/java/com/wellgrounded/hidden/CantTouchThis.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.modlib.hidden; 2 | 3 | public class CantTouchThis { 4 | public static String getMessage() { 5 | return "Hammertime"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch11/maven-modules/mod-lib/src/com.wellgrounded.modlib/java/com/wellgrounded/visible/UseThis.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.modlib.visible; 2 | 3 | public class UseThis { 4 | public static String getMessage() { 5 | return "Modules for fun and profit"; 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /Ch11/maven-modules/mod-lib/src/com.wellgrounded.modlib/java/module-info.java: -------------------------------------------------------------------------------- 1 | module com.wellgrounded.modlib { 2 | exports com.wellgrounded.modlib.visible; 3 | } 4 | -------------------------------------------------------------------------------- /Ch11/maven-modules/non-app/back-to-classpath.sh: -------------------------------------------------------------------------------- 1 | java -cp /Users/jasonclark/source/well-grounded/code/Ch10/maven-modules/non-app/target/nonapp-2.0.jar:../mod-lib/target/modlib-2.0.jar com.wellgrounded.nonapp.Main 2 | -------------------------------------------------------------------------------- /Ch11/maven-modules/non-app/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.wellgrounded 4 | nonapp 5 | 2.0 6 | non-app 7 | 8 | 9 | UTF-8 10 | 11 11 | 11 12 | 13 | 14 | 15 | src/com.wellgrounded.nonapp/java 16 | 17 | 18 | org.apache.maven.plugins 19 | maven-jar-plugin 20 | 2.4 21 | 22 | 23 | 24 | true 25 | com.wellgrounded.nonapp.Main 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | com.wellgrounded 36 | modlib 37 | 2.0 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /Ch11/maven-modules/non-app/src/com.wellgrounded.nonapp/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded.nonapp; 2 | 3 | import com.wellgrounded.modlib.visible.UseThis; 4 | import com.wellgrounded.modlib.hidden.CantTouchThis; 5 | 6 | public class Main { 7 | public static void main(String[] args) throws InterruptedException { 8 | System.out.println(UseThis.getMessage()); 9 | System.out.println(CantTouchThis.getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch11/maven-multi-release/src/main/java/wgjd2ed/GetPID.java: -------------------------------------------------------------------------------- 1 | package wgjd2ed; 2 | 3 | import java.lang.management.ManagementFactory; 4 | 5 | /** 6 | * Java 8 version of GetPID class 7 | * 8 | * @author ben 9 | */ 10 | public class GetPID { 11 | 12 | public static long getPid() { 13 | System.out.println("Java 8 version..."); 14 | // ManagementFactory.getRuntimeMXBean().getName() returns the name that 15 | // represents the currently running JVM. On Sun and Oracle JVMs, this 16 | // name is in the format @. 17 | 18 | final String jvmName = ManagementFactory.getRuntimeMXBean().getName(); 19 | final int index = jvmName.indexOf('@'); 20 | if (index < 1) 21 | return 0; 22 | 23 | try { 24 | return Long.parseLong(jvmName.substring(0, index)); 25 | } catch (NumberFormatException nfe) { 26 | return 0; 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ch11/maven-multi-release/src/main/java/wgjd2ed/Main.java: -------------------------------------------------------------------------------- 1 | package wgjd2ed; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | System.out.println(GetPID.getPid()); 6 | } 7 | } 8 | 9 | -------------------------------------------------------------------------------- /Ch11/maven-multi-release/versions/11/src/wgjd2ed/GetPID.java: -------------------------------------------------------------------------------- 1 | package wgjd2ed; 2 | 3 | /** 4 | * Java 9 GetPID class 5 | * 6 | * @author ben 7 | */ 8 | public class GetPID { 9 | public static long getPid() { 10 | var ph = ProcessHandle.current(); 11 | return ph.pid(); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ch11/wellgrounded-maven-plugin/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | 4 | com.wellgrounded 5 | wellgrounded-maven-plugin 6 | 1.0-SNAPSHOT 7 | maven-plugin 8 | 9 | A Well-Grounded Maven Plugin 10 | 11 | 12 | UTF-8 13 | 11 14 | 11 15 | 16 | 17 | 18 | 19 | org.apache.maven 20 | maven-plugin-api 21 | 3.0 22 | 23 | 24 | 25 | org.apache.maven.plugin-tools 26 | maven-plugin-annotations 27 | 3.4 28 | provided 29 | 30 | 31 | 32 | -------------------------------------------------------------------------------- /Ch11/wellgrounded-maven-plugin/src/main/java/com/wellgrounded/WellGroundedMojo.java: -------------------------------------------------------------------------------- 1 | package sample.plugin; 2 | 3 | import org.apache.maven.plugin.AbstractMojo; 4 | import org.apache.maven.plugin.MojoExecutionException; 5 | import org.apache.maven.plugins.annotations.Mojo; 6 | 7 | @Mojo( name = "wellgrounded") 8 | public class WellGroundedMojo extends AbstractMojo 9 | { 10 | public void execute() throws MojoExecutionException 11 | { 12 | getLog().info("Extending Maven for fun and profit."); 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /Ch12/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 12 - Running Java in Containers 2 | 3 | ## `Ch12` 4 | 5 | This directory contains multiple examples building Docker images using Gradle 6 | and Maven. 7 | 8 | Each individual subproject has its own README with the specific Docker related 9 | commands for running them. Running these commands requires any recent version 10 | of Docker to be available. 11 | 12 | * `docker-gradle` - Basic Gradle application packaged in Docker 13 | * `docker-gradle-multi` - Gradle application with multi-stage Docker build 14 | * `docker-maven` - Basic Maven application packaged in Docker 15 | -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/.dockerignore: -------------------------------------------------------------------------------- 1 | .git 2 | .DS_Store 3 | .idea/ 4 | *.iml 5 | *.class 6 | 7 | # Ignore guides and HOWTOs folder 8 | guides/ 9 | 10 | # Ignore build folders 11 | out/ 12 | build/ 13 | target/ 14 | .gradle/ 15 | 16 | Dockerfile 17 | -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/Dockerfile: -------------------------------------------------------------------------------- 1 | # Use full JDK for building 2 | FROM eclipse-temurin:17-jdk AS build 3 | 4 | RUN mkdir /project 5 | WORKDIR /project 6 | 7 | COPY ./gradle ./gradle 8 | COPY ./gradlew* ./settings.gradle* . 9 | RUN ./gradlew 10 | 11 | # Copy everything into container 12 | COPY . . 13 | 14 | # Build as before 15 | RUN ./gradlew clean installDist 16 | 17 | 18 | # Actual output image now uses JRE for smaller footprint 19 | FROM eclipse-temurin:17-jre 20 | 21 | # /opt/app is where we'll place all of our stuff to run 22 | RUN mkdir /opt/app 23 | 24 | # We grab from our build image results 25 | COPY --from=build /project/build/install/docker-gradle-multi /opt/app/ 26 | 27 | # Uses the generated startup script Gradle creates for us 28 | WORKDIR /opt/app/bin 29 | CMD ["./docker-gradle-multi"] 30 | -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClass.set("com.wellgrounded.Main") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation("org.apache.commons:commons-lang3:3.12.0") 22 | } 23 | -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch12/docker-gradle-multi/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "docker-gradle-multi" 11 | -------------------------------------------------------------------------------- /Ch12/docker-gradle-multi/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpServer; 5 | import org.apache.commons.lang3.tuple.Pair; 6 | 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.logging.ConsoleHandler; 11 | import java.util.logging.FileHandler; 12 | import java.util.logging.Logger; 13 | 14 | public class Main { 15 | private static final Logger logger = Logger.getLogger(Main.class.getName()); 16 | 17 | public static void main(String[] args) throws InterruptedException, IOException { 18 | logger.addHandler(new ConsoleHandler()); 19 | logger.addHandler(new FileHandler("./access.log")); 20 | 21 | var address = new InetSocketAddress("0.0.0.0", 8080); 22 | var server = HttpServer.create(address, 0); 23 | server.createContext("/hello", Main::handle); 24 | server.start(); 25 | 26 | while (true) { 27 | var pair = Pair.of("Howdy", "Docker"); 28 | System.out.println(pair); 29 | Thread.sleep(5000); 30 | } 31 | } 32 | 33 | private static void handle(HttpExchange exchange) throws IOException { 34 | logger.info("Handling an HTTP request"); 35 | 36 | var message = "Hello from HttpServer"; 37 | exchange.sendResponseHeaders(200, message.length()); 38 | 39 | var stream = exchange.getResponseBody(); 40 | stream.write(message.getBytes(StandardCharsets.UTF_8)); 41 | stream.flush(); 42 | stream.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Ch12/docker-gradle/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17-jdk 2 | 3 | # /opt/app is where we'll place all of our stuff to run 4 | RUN mkdir /opt/app 5 | 6 | # We grab all our contents from the location Gradle's installDist places them 7 | COPY build/install/docker-gradle /opt/app/ 8 | 9 | EXPOSE 8080 10 | 11 | # Uses the generated startup script Gradle creates for us 12 | WORKDIR /opt/app/bin 13 | CMD ["./docker-gradle"] 14 | -------------------------------------------------------------------------------- /Ch12/docker-gradle/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClass.set("com.wellgrounded.Main") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation("org.apache.commons:commons-lang3:3.12.0") 22 | } 23 | -------------------------------------------------------------------------------- /Ch12/docker-gradle/docker-compose.yml: -------------------------------------------------------------------------------- 1 | version: "3.9" 2 | services: 3 | app: 4 | build: . 5 | ports: 6 | - "8080:8080" 7 | environment: 8 | REDIS_URL: redis://redis:6379 9 | redis: 10 | image: "redis:alpine" 11 | -------------------------------------------------------------------------------- /Ch12/docker-gradle/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch12/docker-gradle/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch12/docker-gradle/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch12/docker-gradle/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/6.5/userguide/multi_project_builds.html 8 | */ 9 | 10 | rootProject.name = "docker-gradle" 11 | -------------------------------------------------------------------------------- /Ch12/docker-gradle/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpServer; 5 | import org.apache.commons.lang3.tuple.Pair; 6 | 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.logging.ConsoleHandler; 11 | import java.util.logging.FileHandler; 12 | import java.util.logging.Logger; 13 | 14 | public class Main { 15 | private static final Logger logger = Logger.getLogger(Main.class.getName()); 16 | 17 | public static void main(String[] args) throws InterruptedException, IOException { 18 | logger.addHandler(new ConsoleHandler()); 19 | logger.addHandler(new FileHandler("./access.log")); 20 | 21 | var address = new InetSocketAddress("0.0.0.0", 8080); 22 | var server = HttpServer.create(address, 0); 23 | server.createContext("/hello", Main::handle); 24 | server.start(); 25 | 26 | while (true) { 27 | var pair = Pair.of("Howdy", "Docker"); 28 | System.out.println(pair); 29 | Thread.sleep(5000); 30 | } 31 | } 32 | 33 | private static void handle(HttpExchange exchange) throws IOException { 34 | logger.info("Handling an HTTP request"); 35 | 36 | var message = "Hello from HttpServer"; 37 | exchange.sendResponseHeaders(200, message.length()); 38 | 39 | var stream = exchange.getResponseBody(); 40 | stream.write(message.getBytes(StandardCharsets.UTF_8)); 41 | stream.flush(); 42 | stream.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Ch12/docker-maven/Dockerfile: -------------------------------------------------------------------------------- 1 | FROM eclipse-temurin:17-jdk 2 | 3 | # /opt/app is where we'll place all of our stuff to run 4 | RUN mkdir /opt/app 5 | WORKDIR /opt/app 6 | 7 | # We grab all our contents from where Maven puts them 8 | # This assumes that we've run `mvn package dependency:copy-dependencies` 9 | COPY target/*.jar /opt/app/ 10 | COPY target/dependency/*.jar /opt/app/ 11 | 12 | # Enabling debugging 13 | 14 | CMD ["java", "-cp", "./*", "com.wellgrounded.Main"] 15 | -------------------------------------------------------------------------------- /Ch12/docker-maven/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.wellgrounded 4 | docker-maven 5 | 1.0-SNAPSHOT 6 | docker-maven 7 | 8 | 9 | UTF-8 10 | 11 11 | 11 12 | 13 | 14 | 15 | 16 | org.apache.commons 17 | commons-lang3 18 | 3.12.0 19 | 20 | 21 | 22 | -------------------------------------------------------------------------------- /Ch12/docker-maven/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import com.sun.net.httpserver.HttpExchange; 4 | import com.sun.net.httpserver.HttpServer; 5 | import org.apache.commons.lang3.tuple.Pair; 6 | 7 | import java.io.IOException; 8 | import java.net.InetSocketAddress; 9 | import java.nio.charset.StandardCharsets; 10 | import java.util.logging.ConsoleHandler; 11 | import java.util.logging.FileHandler; 12 | import java.util.logging.Logger; 13 | 14 | public class Main { 15 | private static final Logger logger = Logger.getLogger(Main.class.getName()); 16 | 17 | public static void main(String[] args) throws InterruptedException, IOException { 18 | logger.addHandler(new ConsoleHandler()); 19 | logger.addHandler(new FileHandler("./access.log")); 20 | 21 | var address = new InetSocketAddress("0.0.0.0", 8080); 22 | var server = HttpServer.create(address, 0); 23 | server.createContext("/hello", Main::handle); 24 | server.start(); 25 | 26 | while (true) { 27 | var pair = Pair.of("Howdy", "Docker"); 28 | System.out.println(pair); 29 | Thread.sleep(5000); 30 | } 31 | } 32 | 33 | private static void handle(HttpExchange exchange) throws IOException { 34 | logger.info("Handling an HTTP request"); 35 | 36 | var message = "Hello from HttpServer"; 37 | exchange.sendResponseHeaders(200, message.length()); 38 | 39 | var stream = exchange.getResponseBody(); 40 | stream.write(message.getBytes(StandardCharsets.UTF_8)); 41 | stream.flush(); 42 | stream.close(); 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /Ch13/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 13 - Testing fundamentals 2 | 3 | ## `Ch13` 4 | 5 | This directory contains multiple examples around testing and JUnit. 6 | 7 | This entire directory can be loaded in an IDE at the `Ch13` level, or each 8 | subdirectory may be individually loaded itself. Java 11+ is required. 9 | 10 | Builds can be run at the command-line via the typical `mvn verify` or 11 | `./gradlew check` commands. 12 | 13 | * `maven-junit-4` - Maven + JUnit 4 14 | * `maven-junit-5` - Maven + JUnit 5 15 | * `gradle-junit-4` - Gradle + JUnit 4 16 | * `gradle-junit-5` - Gradle + JUnit 5 17 | * `ticketing` - TDD example from the text, JUnit 5 18 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClass.set("com.wellgrounded.Main") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | testImplementation("junit:junit:4.13.2") 22 | } 23 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch13/gradle-junit-4/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "gradle-junit-4" 2 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/src/main/java/com/wellgrounded/PasswordChecker.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PasswordChecker { 7 | private Map stats = new HashMap<>(); 8 | private boolean started = false; 9 | 10 | public void start() { 11 | // Imagine it had state to set up -- reading files, etc. 12 | started = true; 13 | } 14 | 15 | public void stop() { 16 | // Clean up 17 | started = false; 18 | } 19 | 20 | public void reset() { 21 | stats.clear(); 22 | } 23 | 24 | public boolean isOk(String password) { 25 | if (!started) { 26 | throw new IllegalStateException("Server not started"); 27 | } 28 | 29 | if (password == null) { 30 | throw new IllegalArgumentException("password must be non-null"); 31 | } 32 | 33 | return password.length() > 8; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | import org.junit.Test; 5 | 6 | public class MainTest { 7 | @Test 8 | public void checkMessage() { 9 | var unused = ""; 10 | assertNotNull(Main.getMessage()); 11 | } 12 | 13 | public boolean equals(Object anObject) { 14 | return true; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-4/src/test/java/com/wellgrounded/PasswordCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | import org.junit.rules.ExternalResource; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class PasswordCheckerTest { 11 | private PasswordChecker checker = new PasswordChecker(); 12 | 13 | @Rule 14 | public ExternalResource passwordServer = new ExternalResource() { 15 | @Override 16 | protected void before() throws Throwable { 17 | super.before(); 18 | checker.reset(); 19 | checker.start(); 20 | } 21 | 22 | @Override 23 | protected void after() { 24 | super.after(); 25 | checker.stop(); 26 | } 27 | }; 28 | 29 | @Rule 30 | public ExpectedException exceptions = ExpectedException.none(); 31 | 32 | @Test 33 | public void ok() { 34 | assertTrue(checker.isOk("abcd1234!")); 35 | } 36 | 37 | @Test 38 | public void tooShort() { 39 | assertFalse(checker.isOk("abcd")); 40 | } 41 | 42 | @Test 43 | public void nullThrows() { 44 | exceptions.expect(IllegalArgumentException.class); 45 | checker.isOk(null); 46 | } 47 | 48 | @Test(expected = IllegalArgumentException.class) 49 | public void alsoThrows() { 50 | checker.isOk(null); 51 | } 52 | } -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClass.set("com.wellgrounded.Main") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | // During migration, we can mix and match JUnit 4 + 5 22 | testImplementation("junit:junit:4.13.2") 23 | testRuntimeOnly("org.junit.vintage:junit-vintage-engine:5.7.1") 24 | 25 | // Once we're all on JUnit 5, only these should be needed 26 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1") 27 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.1") 28 | } 29 | 30 | tasks.named("test") { 31 | useJUnitPlatform() 32 | } 33 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch13/gradle-junit-5/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "gradle-junit-5" 2 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/src/main/java/com/wellgrounded/PasswordChecker.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PasswordChecker { 7 | private static Map stats = new HashMap<>(); 8 | private boolean started; 9 | 10 | public void start() { 11 | // Imagine it had state to set up -- reading files, etc. 12 | started = true; 13 | } 14 | 15 | public void stop() { 16 | // Clean up 17 | started = false; 18 | } 19 | 20 | public long getStat(Boolean key) { 21 | return stats.getOrDefault(key, 0L); 22 | } 23 | 24 | public void reset() { 25 | stats.clear(); 26 | } 27 | 28 | public boolean isOk(String password) { 29 | if (!started) { 30 | throw new IllegalStateException("Server not started"); 31 | } 32 | 33 | if (password == null) { 34 | increment(false); 35 | throw new IllegalArgumentException("password must be non-null"); 36 | } 37 | 38 | var result = password.length() > 8; 39 | increment(result); 40 | 41 | return result; 42 | } 43 | 44 | private void increment(Boolean key) { 45 | var count = stats.getOrDefault(key, 0L); 46 | stats.put(key, count + 1); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | import org.junit.Test; 5 | 6 | public class MainTest { 7 | @Test 8 | public void checkMessage() { 9 | var unused = ""; 10 | assertNotNull(Main.getMessage()); 11 | } 12 | 13 | public boolean equals(Object anObject) { 14 | return true; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/src/test/java/com/wellgrounded/PasswordCheckerExtension.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.extension.AfterEachCallback; 4 | import org.junit.jupiter.api.extension.BeforeEachCallback; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | public class PasswordCheckerExtension implements AfterEachCallback, BeforeEachCallback { 8 | private PasswordChecker checker; 9 | 10 | PasswordCheckerExtension(PasswordChecker checker) { 11 | this.checker = checker; 12 | } 13 | 14 | @Override 15 | public void beforeEach(ExtensionContext context) { 16 | checker.reset(); 17 | checker.start(); 18 | } 19 | 20 | @Override 21 | public void afterEach(ExtensionContext context) { 22 | checker.stop(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch13/gradle-junit-5/src/test/java/com/wellgrounded/PasswordCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.RegisterExtension; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class PasswordCheckerTest { 10 | private static PasswordChecker checker = new PasswordChecker(); 11 | 12 | @RegisterExtension 13 | static PasswordCheckerExtension ext = new PasswordCheckerExtension(checker); 14 | 15 | @BeforeEach 16 | public void setup() { 17 | checker.reset(); 18 | } 19 | 20 | @Test 21 | public void ok() { 22 | assertTrue(checker.isOk("abcd1234!")); 23 | assertEquals(1L, checker.getStat(true)); 24 | assertEquals(0L, checker.getStat(false)); 25 | } 26 | 27 | @Test 28 | public void tooShort() { 29 | assertFalse(checker.isOk("abcd")); 30 | assertEquals(0L, checker.getStat(true)); 31 | assertEquals(1L, checker.getStat(false)); 32 | } 33 | 34 | @Test 35 | public void nullThrows() { 36 | assertThrows(IllegalArgumentException.class, () -> { 37 | checker.isOk(null); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Ch13/maven-junit-4/pom.xml: -------------------------------------------------------------------------------- 1 | 2 | 4.0.0 3 | com.wellgrounded 4 | maven-junit-4 5 | 1.0-SNAPSHOT 6 | maven-junit-4 7 | 8 | 9 | UTF-8 10 | 11 11 | 11 12 | 13 | 14 | 15 | 16 | junit 17 | junit 18 | 4.13.2 19 | test 20 | 21 | 22 | 23 | -------------------------------------------------------------------------------- /Ch13/maven-junit-4/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch13/maven-junit-4/src/main/java/com/wellgrounded/PasswordChecker.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PasswordChecker { 7 | private Map stats = new HashMap<>(); 8 | private boolean started = false; 9 | 10 | public void start() { 11 | // Imagine it had state to set up -- reading files, etc. 12 | started = true; 13 | } 14 | 15 | public void stop() { 16 | // Clean up 17 | started = false; 18 | } 19 | 20 | public void reset() { 21 | stats.clear(); 22 | } 23 | 24 | public boolean isOk(String password) { 25 | if (!started) { 26 | throw new IllegalStateException("Server not started"); 27 | } 28 | 29 | if (password == null) { 30 | throw new IllegalArgumentException("password must be non-null"); 31 | } 32 | 33 | return password.length() > 8; 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ch13/maven-junit-4/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | import org.junit.Test; 5 | 6 | public class MainTest { 7 | @Test 8 | public void checkMessage() { 9 | var unused = ""; 10 | assertNotNull(Main.getMessage()); 11 | } 12 | 13 | public boolean equals(Object anObject) { 14 | return true; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ch13/maven-junit-4/src/test/java/com/wellgrounded/PasswordCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.Rule; 4 | import org.junit.Test; 5 | import org.junit.rules.ExpectedException; 6 | import org.junit.rules.ExternalResource; 7 | 8 | import static org.junit.Assert.*; 9 | 10 | public class PasswordCheckerTest { 11 | private PasswordChecker checker = new PasswordChecker(); 12 | 13 | @Rule 14 | public ExternalResource passwordServer = new ExternalResource() { 15 | @Override 16 | protected void before() throws Throwable { 17 | super.before(); 18 | checker.reset(); 19 | checker.start(); 20 | } 21 | 22 | @Override 23 | protected void after() { 24 | super.after(); 25 | checker.stop(); 26 | } 27 | }; 28 | 29 | @Rule 30 | public ExpectedException exceptions = ExpectedException.none(); 31 | 32 | @Test 33 | public void ok() { 34 | assertTrue(checker.isOk("abcd1234!")); 35 | } 36 | 37 | @Test 38 | public void tooShort() { 39 | assertFalse(checker.isOk("abcd")); 40 | } 41 | 42 | @Test 43 | public void nullThrows() { 44 | exceptions.expect(IllegalArgumentException.class); 45 | checker.isOk(null); 46 | } 47 | 48 | @Test(expected = IllegalArgumentException.class) 49 | public void alsoThrows() { 50 | checker.isOk(null); 51 | } 52 | } -------------------------------------------------------------------------------- /Ch13/maven-junit-5/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch13/maven-junit-5/src/main/java/com/wellgrounded/PasswordChecker.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.util.HashMap; 4 | import java.util.Map; 5 | 6 | public class PasswordChecker { 7 | private static Map stats = new HashMap<>(); 8 | private boolean started; 9 | 10 | public void start() { 11 | // Imagine it had state to set up -- reading files, etc. 12 | started = true; 13 | } 14 | 15 | public void stop() { 16 | // Clean up 17 | started = false; 18 | } 19 | 20 | public long getStat(Boolean key) { 21 | return stats.getOrDefault(key, 0L); 22 | } 23 | 24 | public void reset() { 25 | stats.clear(); 26 | } 27 | 28 | public boolean isOk(String password) { 29 | if (!started) { 30 | throw new IllegalStateException("Server not started"); 31 | } 32 | 33 | if (password == null) { 34 | increment(false); 35 | throw new IllegalArgumentException("password must be non-null"); 36 | } 37 | 38 | var result = password.length() > 8; 39 | increment(result); 40 | 41 | return result; 42 | } 43 | 44 | private void increment(Boolean key) { 45 | var count = stats.getOrDefault(key, 0L); 46 | stats.put(key, count + 1); 47 | } 48 | } 49 | -------------------------------------------------------------------------------- /Ch13/maven-junit-5/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import static org.junit.Assert.assertNotNull; 4 | import org.junit.Test; 5 | 6 | public class MainTest { 7 | @Test 8 | public void checkMessage() { 9 | var unused = ""; 10 | assertNotNull(Main.getMessage()); 11 | } 12 | 13 | public boolean equals(Object anObject) { 14 | return true; 15 | } 16 | } 17 | -------------------------------------------------------------------------------- /Ch13/maven-junit-5/src/test/java/com/wellgrounded/PasswordCheckerExtension.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.extension.AfterEachCallback; 4 | import org.junit.jupiter.api.extension.BeforeEachCallback; 5 | import org.junit.jupiter.api.extension.ExtensionContext; 6 | 7 | public class PasswordCheckerExtension implements AfterEachCallback, BeforeEachCallback { 8 | private PasswordChecker checker; 9 | 10 | PasswordCheckerExtension(PasswordChecker checker) { 11 | this.checker = checker; 12 | } 13 | 14 | @Override 15 | public void beforeEach(ExtensionContext context) { 16 | checker.reset(); 17 | checker.start(); 18 | } 19 | 20 | @Override 21 | public void afterEach(ExtensionContext context) { 22 | checker.stop(); 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Ch13/maven-junit-5/src/test/java/com/wellgrounded/PasswordCheckerTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | import org.junit.jupiter.api.extension.RegisterExtension; 6 | 7 | import static org.junit.jupiter.api.Assertions.*; 8 | 9 | public class PasswordCheckerTest { 10 | private static PasswordChecker checker = new PasswordChecker(); 11 | 12 | @RegisterExtension 13 | static PasswordCheckerExtension ext = new PasswordCheckerExtension(checker); 14 | 15 | @BeforeEach 16 | public void setup() { 17 | checker.reset(); 18 | } 19 | 20 | @Test 21 | public void ok() { 22 | assertTrue(checker.isOk("abcd1234!")); 23 | assertEquals(1L, checker.getStat(true)); 24 | assertEquals(0L, checker.getStat(false)); 25 | } 26 | 27 | @Test 28 | public void tooShort() { 29 | assertFalse(checker.isOk("abcd")); 30 | assertEquals(0L, checker.getStat(true)); 31 | assertEquals(1L, checker.getStat(false)); 32 | } 33 | 34 | @Test 35 | public void nullThrows() { 36 | assertThrows(IllegalArgumentException.class, () -> { 37 | checker.isOk(null); 38 | }); 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /Ch13/ticketing/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClass.set("com.wellgrounded.Main") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") 22 | testImplementation("org.mockito:mockito-core:4.1.0") 23 | 24 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") 25 | } 26 | 27 | tasks.named("test") { 28 | useJUnitPlatform() 29 | } -------------------------------------------------------------------------------- /Ch13/ticketing/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch13/ticketing/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch13/ticketing/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch13/ticketing/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "ticketing" 2 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/HttpPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPrice implements Price { 6 | @Override 7 | public BigDecimal getInitialPrice() { 8 | return HttpPricingService.getInitialPrice(); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/HttpPricingService.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPricingService { 6 | public static BigDecimal getInitialPrice() { 7 | return new BigDecimal(30); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/Price.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public interface Price { 6 | BigDecimal getInitialPrice(); 7 | } -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/Show.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class Show { 6 | private TicketDatabase db; 7 | private int capacity; 8 | 9 | public Show(TicketDatabase db, int capacity) { 10 | this.db = db; 11 | this.capacity = capacity; 12 | } 13 | 14 | public void addTicket(String name, BigDecimal amount) { 15 | if (db.count() < capacity) { 16 | var ticket = new Ticket(name, amount); 17 | db.insert(ticket); 18 | } else { 19 | throw new RuntimeException("Oversold"); 20 | } 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/TicketDatabase.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public interface TicketDatabase { 4 | Ticket findById(int id); 5 | Ticket findByName(String name); 6 | int count(); 7 | 8 | void insert(Ticket ticket); 9 | void delete(int id); 10 | } -------------------------------------------------------------------------------- /Ch13/ticketing/src/main/java/com/wellgrounded/TicketRevenue.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class TicketRevenue { 6 | private final static int TICKET_PRICE = 30; 7 | 8 | public BigDecimal estimateTotalRevenue(int numberOfTicketsSold) 9 | throws IllegalArgumentException { 10 | 11 | if (numberOfTicketsSold < 0 || numberOfTicketsSold > 100) { 12 | throw new IllegalArgumentException("# Tix sold must == 1..100"); 13 | } 14 | 15 | return new BigDecimal(TICKET_PRICE * numberOfTicketsSold); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/test/java/com/wellgrounded/FakeTicketDatabase.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.util.HashMap; 4 | 5 | class FakeTicketDatabase implements TicketDatabase { 6 | private HashMap tickets = new HashMap<>(); 7 | private Integer nextId = 1; 8 | 9 | @Override 10 | public Ticket findByName(String name) { 11 | var found = tickets.values() 12 | .stream() 13 | .filter(ticket -> ticket.getClientName().equals(name)) 14 | .findFirst(); 15 | return found.orElse(null); 16 | } 17 | 18 | @Override 19 | public int count() { 20 | return tickets.size(); 21 | } 22 | 23 | @Override 24 | public void insert(Ticket ticket) { 25 | tickets.put(nextId, ticket); 26 | nextId++; 27 | } 28 | 29 | @Override 30 | public Ticket findById(int id) { 31 | return tickets.get(id); 32 | } 33 | 34 | @Override 35 | public void delete(int id) { 36 | tickets.remove(id); 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/test/java/com/wellgrounded/MainTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import static org.junit.jupiter.api.Assertions.assertNotNull; 6 | 7 | public class MainTest { 8 | @Test 9 | public void checkMessage() { 10 | var unused = ""; 11 | assertNotNull(Main.getMessage()); 12 | } 13 | 14 | public boolean equals(Object anObject) { 15 | return true; 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/test/java/com/wellgrounded/ShowTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | 9 | public class ShowTest { 10 | @Test 11 | public void plentyOfSpace() { 12 | var db = new FakeTicketDatabase(); 13 | var show = new Show(db, 5); 14 | 15 | var name = "New One"; 16 | show.addTicket(name, BigDecimal.ONE); 17 | 18 | var mine = db.findByName(name); 19 | assertEquals(name, mine.getClientName()); 20 | assertEquals(BigDecimal.ONE, mine.getPrice()); 21 | } 22 | } -------------------------------------------------------------------------------- /Ch13/ticketing/src/test/java/com/wellgrounded/StubPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class StubPrice implements Price { 6 | @Override 7 | public BigDecimal getInitialPrice() { 8 | return new BigDecimal("10"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch13/ticketing/src/test/java/com/wellgrounded/TicketRevenueTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.BeforeEach; 4 | import org.junit.jupiter.api.Test; 5 | 6 | import java.math.BigDecimal; 7 | 8 | import static org.junit.jupiter.api.Assertions.assertEquals; 9 | import static org.junit.jupiter.api.Assertions.assertThrows; 10 | 11 | public class TicketRevenueTest { 12 | private TicketRevenue venueRevenue; 13 | private BigDecimal expectedRevenue; 14 | 15 | @BeforeEach 16 | public void setUp() { 17 | venueRevenue = new TicketRevenue(); 18 | } 19 | 20 | @Test 21 | public void failIfLessThanZeroTicketsAreSold() { 22 | assertThrows(IllegalArgumentException.class, 23 | () -> venueRevenue.estimateTotalRevenue(-1)); 24 | } 25 | 26 | @Test 27 | public void zeroSalesEqualsZeroRevenue() { 28 | assertEquals(BigDecimal.ZERO, venueRevenue.estimateTotalRevenue(0)); 29 | } 30 | 31 | @Test 32 | public void oneTicketSoldIsThirtyInRevenue() { 33 | expectedRevenue = new BigDecimal("30"); 34 | assertEquals(expectedRevenue, venueRevenue.estimateTotalRevenue(1)); 35 | } 36 | 37 | @Test 38 | public void tenTicketsSoldIsThreeHundredInRevenue() { 39 | expectedRevenue = new BigDecimal("300"); 40 | assertEquals(expectedRevenue, venueRevenue.estimateTotalRevenue(10)); 41 | } 42 | 43 | @Test 44 | public void failIfMoreThanOneHundredTicketsAreSold() { 45 | assertThrows(IllegalArgumentException.class, 46 | () -> venueRevenue.estimateTotalRevenue(101)); 47 | } 48 | } -------------------------------------------------------------------------------- /Ch13/ticketing/src/test/java/com/wellgrounded/TicketTest.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import org.junit.jupiter.api.Test; 4 | 5 | import java.math.BigDecimal; 6 | 7 | import static org.junit.jupiter.api.Assertions.assertEquals; 8 | import static org.mockito.Mockito.*; 9 | 10 | public class TicketTest { 11 | @Test 12 | public void tenPercentDiscount() { 13 | Price price = new HttpPrice(); 14 | Ticket ticket = new Ticket(price, new BigDecimal("0.9")); 15 | assertEquals(new BigDecimal("27.0"), ticket.getDiscountPrice()); 16 | } 17 | 18 | @Test 19 | public void tenPercentDiscountStubbed() { 20 | Price price = new StubPrice(); 21 | Ticket ticket = new Ticket(price, new BigDecimal("0.9")); 22 | assertEquals(new BigDecimal("9.0"), ticket.getDiscountPrice()); 23 | } 24 | 25 | @Test 26 | public void tenPercentDiscountMocked() { 27 | Price price = mock(Price.class); 28 | 29 | when(price.getInitialPrice()).thenReturn(new BigDecimal("10")); 30 | 31 | Ticket ticket = new Ticket(price, new BigDecimal("0.9")); 32 | assertEquals(new BigDecimal("9.0"), ticket.getDiscountPrice()); 33 | 34 | verify(price).getInitialPrice(); 35 | } 36 | } 37 | -------------------------------------------------------------------------------- /Ch14/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 14 - Testing beyond JUnit 2 | 3 | ## `Ch14` 4 | 5 | This directory contains multiple examples of other testing approaches beyond 6 | the basics of JUnit. 7 | 8 | This entire directory can be loaded in an IDE at the `Ch14` level, or each 9 | subdirectory may be individually loaded itself. Java 11+ is required. 10 | 11 | * `testcontainers` - Integration testing with testcontainers. Can be run with `./gradlew check` 12 | * `spek-gradle` - Spek framework integrated with Gradle. Can be run with `./gradlew check` 13 | * `spek-maven` - Spek framework integrated with Maven. Can be run with `mvn verify` 14 | 15 | ## `Ch14/clj-testing` 16 | 17 | This example shows Clojure testing with the built-in framework and property 18 | testing via `test.check`. 19 | 20 | You'll need to [install Clojure](https://clojure.org/guides/getting_started) if 21 | you haven't already before running this example. These resources have been 22 | checked with version 1.10 of Clojure. 23 | 24 | Tests may be run by the following: 25 | 26 | ``` 27 | cd Ch14/clj-testing 28 | clj -M test/first_test.clj 29 | ``` 30 | 31 | This may occasionally fail with the message `Couldn't satisfy such-that 32 | predicate after 100 tries.` which is an indication our property generation 33 | could use some fine-tuning. 34 | 35 | Additionally there are scripts for fancier Clojure test runners under the 36 | `Ch14/clj-testing/bin` directory. 37 | -------------------------------------------------------------------------------- /Ch14/clj-testing/README.md: -------------------------------------------------------------------------------- 1 | To run tests using the cognitect-labs test-runner: 2 | 3 | ``` 4 | bin/cognitect-test 5 | ``` 6 | 7 | To run tests with kaocha: 8 | 9 | ``` 10 | bin/kaocha 11 | ``` 12 | -------------------------------------------------------------------------------- /Ch14/clj-testing/bin/cognitect-test: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | clj -M:cognitect-test -d test "$@" 3 | -------------------------------------------------------------------------------- /Ch14/clj-testing/bin/kaocha: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | clj -M:kaocha -m kaocha.runner "$@" 3 | -------------------------------------------------------------------------------- /Ch14/clj-testing/deps.edn: -------------------------------------------------------------------------------- 1 | { 2 | :deps { org.clojure/test.check {:mvn/version "1.1.0"}} 3 | :aliases { 4 | :cognitect-test {:extra-paths ["test"] 5 | :extra-deps {io.github.cognitect-labs/test-runner 6 | {:git/url "https://github.com/cognitect-labs/test-runner.git" 7 | :sha "8c3f22363d63715de4087b038d79ae0de36a3263"}} 8 | :main-opts ["-m" "cognitect.test-runner"] 9 | :exec-fn cognitect.test-runner.api/test} 10 | :kaocha {:extra-deps { 11 | lambdaisland/kaocha {:mvn/version "1.0.861"} 12 | }}} 13 | } 14 | -------------------------------------------------------------------------------- /Ch14/clj-testing/tests.edn: -------------------------------------------------------------------------------- 1 | #kaocha/v1 {} 2 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | kotlin("jvm") version "1.6.10" 5 | } 6 | 7 | application { 8 | mainClass.set("com.wellgrounded.Main") 9 | } 10 | 11 | tasks.jar { 12 | manifest { 13 | attributes("Main-Class" to application.mainClass) 14 | } 15 | } 16 | 17 | repositories { 18 | mavenCentral() 19 | } 20 | 21 | dependencies { 22 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1") 23 | 24 | testImplementation("org.spekframework.spek2:spek-dsl-jvm:2.0.15") 25 | testRuntimeOnly("org.spekframework.spek2:spek-runner-junit5:2.0.15") 26 | } 27 | 28 | tasks.named("test") { 29 | useJUnitPlatform() { 30 | includeEngines("spek2") 31 | } 32 | } 33 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch14/spek-gradle/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch14/spek-gradle/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "spek" 2 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/main/java/com/wellgrounded/HttpPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPrice implements Price { 6 | @Override 7 | public BigDecimal getInitialPrice() { 8 | return HttpPricingService.getInitialPrice(); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/main/java/com/wellgrounded/HttpPricingService.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPricingService { 6 | public static BigDecimal getInitialPrice() { 7 | return new BigDecimal(10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/main/java/com/wellgrounded/InMemoryCachedPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class InMemoryCachedPrice implements Price { 6 | private final Price priceLookup; 7 | 8 | private BigDecimal cached; 9 | 10 | InMemoryCachedPrice(Price priceLookup) { 11 | this(priceLookup, null); 12 | } 13 | 14 | InMemoryCachedPrice(Price priceLookup, BigDecimal cached) { 15 | this.priceLookup = priceLookup; 16 | this.cached = cached; 17 | } 18 | 19 | @Override 20 | public BigDecimal getInitialPrice() { 21 | if (cached == null) { 22 | cached = priceLookup.getInitialPrice(); 23 | } 24 | 25 | return cached; 26 | } 27 | 28 | void clearCache() { 29 | cached = null; 30 | } 31 | } -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/main/java/com/wellgrounded/Price.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public interface Price { 6 | BigDecimal getInitialPrice(); 7 | } 8 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/test/kotlin/com/wellgrounded/InMemoryCachedPriceSpek.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import org.junit.jupiter.api.Assertions.* 4 | import org.spekframework.spek2.Spek 5 | 6 | import java.math.BigDecimal 7 | 8 | object InMemoryCachedPriceSpek : Spek({ 9 | val stubbedPrice : Price by memoized { StubPrice() } 10 | 11 | group("empty cache") { 12 | val cachedPrice by memoized { InMemoryCachedPrice(stubbedPrice) } 13 | 14 | test("gets default value") { 15 | assertEquals(BigDecimal(10), cachedPrice.initialPrice) 16 | } 17 | 18 | test("returns exact same object") { 19 | val first = cachedPrice.initialPrice 20 | val second = cachedPrice.initialPrice 21 | assertTrue(first === second) 22 | } 23 | 24 | listOf(1, 2, 3).forEach { 25 | test("testing $it") { 26 | assertNotEquals(BigDecimal(it), cachedPrice.initialPrice) 27 | } 28 | } 29 | } 30 | 31 | //group("existing cache") { 32 | // lateinit var cachedPrice : InMemoryCachedPrice 33 | 34 | // beforeEachTest { 35 | // cachedPrice = InMemoryCachedPrice(stubbedPrice, BigDecimal(20)) 36 | // } 37 | 38 | // test("gets cached value") { 39 | // assertEquals(BigDecimal(20), cachedPrice.initialPrice) 40 | // } 41 | //} 42 | }) 43 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/test/kotlin/com/wellgrounded/InMemoryCachedPriceSpekGherkin.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import org.spekframework.spek2.Spek 4 | 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.spekframework.spek2.style.gherkin.Feature 7 | import java.math.BigDecimal 8 | 9 | object InMemoryCachedPriceSpekGherkin : Spek({ 10 | Feature("caching") { 11 | val stubbedPrice by memoized { StubPrice() } 12 | 13 | lateinit var cachedPrice : Price 14 | lateinit var result : BigDecimal 15 | 16 | Scenario("empty cache") { 17 | Given("an empty cache") { 18 | cachedPrice = InMemoryCachedPrice(stubbedPrice) 19 | } 20 | 21 | When("calculating") { 22 | result = cachedPrice.initialPrice 23 | } 24 | 25 | Then("it looks up from original source") { 26 | assertEquals(BigDecimal(10), result) 27 | } 28 | } 29 | 30 | Scenario("cached value") { 31 | Given("a cached price") { 32 | cachedPrice = InMemoryCachedPrice(stubbedPrice, BigDecimal(20)) 33 | } 34 | 35 | When("calculating") { 36 | result = cachedPrice.initialPrice 37 | } 38 | 39 | Then("it finds in the cache") { 40 | assertEquals(BigDecimal(20), result) 41 | } 42 | } 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/test/kotlin/com/wellgrounded/InMemoryCachedPriceSpekSpecification.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import org.spekframework.spek2.Spek 4 | 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.spekframework.spek2.style.gherkin.Feature 7 | import org.spekframework.spek2.style.specification.describe 8 | import java.math.BigDecimal 9 | 10 | object InMemoryCachedPriceSpekSpecification : Spek({ 11 | describe("caching") { 12 | val stubbedPrice by memoized { StubPrice() } 13 | 14 | describe("empty cache") { 15 | val cachedPrice by memoized { InMemoryCachedPrice(stubbedPrice) } 16 | 17 | it("looks up the value") { 18 | assertEquals(BigDecimal(10), cachedPrice.initialPrice) 19 | } 20 | } 21 | 22 | describe("cached value") { 23 | val cachedPrice by memoized { InMemoryCachedPrice(stubbedPrice, BigDecimal(20)) } 24 | 25 | it("looks up the value") { 26 | assertEquals(BigDecimal(20), cachedPrice.initialPrice) 27 | } 28 | } 29 | } 30 | }) -------------------------------------------------------------------------------- /Ch14/spek-gradle/src/test/kotlin/com/wellgrounded/StubPrice.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import java.math.BigDecimal 4 | 5 | class StubPrice : Price { 6 | override fun getInitialPrice(): BigDecimal { 7 | return BigDecimal("10") 8 | } 9 | } -------------------------------------------------------------------------------- /Ch14/spek-maven/src/main/java/com/wellgrounded/HttpPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPrice implements Price { 6 | @Override 7 | public BigDecimal getInitialPrice() { 8 | return HttpPricingService.getInitialPrice(); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Ch14/spek-maven/src/main/java/com/wellgrounded/HttpPricingService.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPricingService { 6 | public static BigDecimal getInitialPrice() { 7 | return new BigDecimal(10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch14/spek-maven/src/main/java/com/wellgrounded/InMemoryCachedPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class InMemoryCachedPrice implements Price { 6 | private final Price priceLookup; 7 | 8 | private BigDecimal cached; 9 | 10 | InMemoryCachedPrice(Price priceLookup) { 11 | this(priceLookup, null); 12 | } 13 | 14 | InMemoryCachedPrice(Price priceLookup, BigDecimal cached) { 15 | this.priceLookup = priceLookup; 16 | this.cached = cached; 17 | } 18 | 19 | @Override 20 | public BigDecimal getInitialPrice() { 21 | if (cached == null) { 22 | cached = priceLookup.getInitialPrice(); 23 | } 24 | 25 | return cached; 26 | } 27 | 28 | void clearCache() { 29 | cached = null; 30 | } 31 | } -------------------------------------------------------------------------------- /Ch14/spek-maven/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch14/spek-maven/src/main/java/com/wellgrounded/Price.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public interface Price { 6 | BigDecimal getInitialPrice(); 7 | } 8 | -------------------------------------------------------------------------------- /Ch14/spek-maven/src/test/kotlin/com/wellgrounded/InMemoryCachedPriceSpek.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import org.spekframework.spek2.Spek 4 | 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.junit.jupiter.api.Assertions.assertNotEquals 7 | import org.junit.jupiter.api.Assertions.assertTrue 8 | import java.math.BigDecimal 9 | 10 | object InMemoryCachedPriceSpek : Spek({ 11 | val stubbedPrice by memoized { StubPrice() } 12 | 13 | group("empty cache") { 14 | val cachedPrice by memoized { InMemoryCachedPrice(stubbedPrice) } 15 | 16 | test("gets default value") { 17 | assertEquals(BigDecimal(10), cachedPrice.initialPrice) 18 | } 19 | 20 | test("gets same value when called again") { 21 | val first = cachedPrice.initialPrice 22 | val second = cachedPrice.initialPrice 23 | assertTrue(first === second) 24 | } 25 | 26 | listOf(1, 2, 3).forEach { 27 | test("testing $it") { 28 | assertNotEquals(BigDecimal(it), cachedPrice.initialPrice) 29 | } 30 | } 31 | } 32 | 33 | group("existing cache") { 34 | lateinit var cachedPrice : InMemoryCachedPrice 35 | 36 | beforeEachTest { 37 | cachedPrice = InMemoryCachedPrice(stubbedPrice, BigDecimal(20)) 38 | } 39 | 40 | test("gets cached value") { 41 | assertEquals(BigDecimal(20), cachedPrice.initialPrice) 42 | } 43 | } 44 | }) 45 | -------------------------------------------------------------------------------- /Ch14/spek-maven/src/test/kotlin/com/wellgrounded/InMemoryCachedPriceSpekSpecification.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import org.spekframework.spek2.Spek 4 | 5 | import org.junit.jupiter.api.Assertions.assertEquals 6 | import org.spekframework.spek2.style.gherkin.Feature 7 | import org.spekframework.spek2.style.specification.describe 8 | import java.math.BigDecimal 9 | 10 | object InMemoryCachedPriceSpekSpecification : Spek({ 11 | describe("caching") { 12 | val stubbedPrice by memoized { StubPrice() } 13 | 14 | describe("empty cache") { 15 | val cachedPrice by memoized { InMemoryCachedPrice(stubbedPrice) } 16 | 17 | it("looks up the value") { 18 | assertEquals(BigDecimal(10), cachedPrice.initialPrice) 19 | } 20 | } 21 | 22 | describe("cached value") { 23 | val cachedPrice by memoized { InMemoryCachedPrice(stubbedPrice, BigDecimal(20)) } 24 | 25 | it("looks up the value") { 26 | assertEquals(BigDecimal(20), cachedPrice.initialPrice) 27 | } 28 | } 29 | } 30 | }) -------------------------------------------------------------------------------- /Ch14/spek-maven/src/test/kotlin/com/wellgrounded/StubPrice.kt: -------------------------------------------------------------------------------- 1 | package com.wellgrounded 2 | 3 | import java.math.BigDecimal 4 | 5 | class StubPrice : Price { 6 | override fun getInitialPrice(): BigDecimal { 7 | return BigDecimal("10") 8 | } 9 | } -------------------------------------------------------------------------------- /Ch14/testcontainers/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClass.set("com.wellgrounded.Main") 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClass) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | // Redis 22 | implementation("redis.clients:jedis:3.6.0") 23 | 24 | // Postgres 25 | implementation("org.postgresql:postgresql:42.2.1") 26 | 27 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.7.1") 28 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.7.1") 29 | 30 | testImplementation("org.testcontainers:testcontainers:1.15.3") 31 | testImplementation("org.testcontainers:junit-jupiter:1.15.3") 32 | 33 | testImplementation("org.testcontainers:postgresql:1.15.3") 34 | 35 | testImplementation("org.testcontainers:selenium:1.15.3") 36 | testImplementation("org.seleniumhq.selenium:selenium-remote-driver:3.141.59") 37 | testImplementation("org.seleniumhq.selenium:selenium-chrome-driver:3.141.59") 38 | } 39 | 40 | tasks.named("test") { 41 | useJUnitPlatform() 42 | } 43 | -------------------------------------------------------------------------------- /Ch14/testcontainers/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch14/testcontainers/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch14/testcontainers/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch14/testcontainers/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "testcontainers" 2 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/main/java/com/wellgrounded/CachedPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import redis.clients.jedis.Jedis; 4 | 5 | import java.math.BigDecimal; 6 | 7 | public class CachedPrice implements Price { 8 | private final Price priceLookup; 9 | private final Jedis cacheClient; 10 | 11 | private static final String priceKey = "price"; 12 | 13 | CachedPrice(Price priceLookup, Jedis cacheClient) { 14 | this.priceLookup = priceLookup; 15 | this.cacheClient = cacheClient; 16 | } 17 | 18 | @Override 19 | public BigDecimal getInitialPrice() { 20 | String cachedPrice = cacheClient.get(priceKey); 21 | if (cachedPrice != null) { 22 | return new BigDecimal(cachedPrice); 23 | } 24 | 25 | BigDecimal price = priceLookup.getInitialPrice(); 26 | cacheClient.set(priceKey, price.toPlainString()); 27 | return price; 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/main/java/com/wellgrounded/HttpPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPrice implements Price { 6 | @Override 7 | public BigDecimal getInitialPrice() { 8 | return HttpPricingService.getInitialPrice(); 9 | } 10 | } 11 | 12 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/main/java/com/wellgrounded/HttpPricingService.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class HttpPricingService { 6 | public static BigDecimal getInitialPrice() { 7 | return new BigDecimal(10); 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/main/java/com/wellgrounded/Main.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | public class Main { 4 | public static String getMessage() { 5 | return "Gradle for fun and profit"; 6 | } 7 | 8 | public static void main(String[] args) { 9 | System.out.println(getMessage()); 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/main/java/com/wellgrounded/Price.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public interface Price { 6 | BigDecimal getInitialPrice(); 7 | } 8 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/main/resources/init.sql: -------------------------------------------------------------------------------- 1 | CREATE TABLE IF NOT EXISTS prices ( 2 | price decimal NOT NULL, 3 | seen_at timestamp NOT NULL DEFAULT NOW() 4 | ); 5 | -------------------------------------------------------------------------------- /Ch14/testcontainers/src/test/java/com/wellgrounded/StubPrice.java: -------------------------------------------------------------------------------- 1 | package com.wellgrounded; 2 | 3 | import java.math.BigDecimal; 4 | 5 | public class StubPrice implements Price { 6 | @Override 7 | public BigDecimal getInitialPrice() { 8 | return new BigDecimal("10"); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch15/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 15 - Advanced functional programming 2 | 3 | ## `Ch15` 4 | 5 | This directory contains multiple examples showing various functional 6 | programming concepts. 7 | 8 | This directory can be loaded in an IDE at the `Ch15` level which is recommended 9 | as a few examples require dependencies. Java 11+ is required. 10 | 11 | * `ch15/Ch15Examples.java` - Basic examples from the text 12 | * `ch15/ClosureExamples.java` - Basic examples of closing over a value 13 | * `ch15/DepositMain.java` - Example of reflective access to classes 14 | * `ch15/PrefixerMain.java` - Example of a functional string prefixer 15 | * `ch15/StreamExamples.java` - Example of using function chaining on streams 16 | * `ch15/TailRecASM.java` - Example rewriting a tail recursive function in bytecode 17 | * `ch15/TailRecNaive.java` - Example of tail recursive function which still fails 18 | -------------------------------------------------------------------------------- /Ch15/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | application 3 | java 4 | } 5 | 6 | application { 7 | mainClassName = "ch15.Ch15Examples" 8 | } 9 | 10 | tasks.jar { 11 | manifest { 12 | attributes("Main-Class" to application.mainClassName) 13 | } 14 | } 15 | 16 | repositories { 17 | mavenCentral() 18 | } 19 | 20 | dependencies { 21 | implementation("org.ow2.asm:asm:9.2") 22 | 23 | testImplementation("org.junit.jupiter:junit-jupiter-api:5.8.1") 24 | testImplementation("org.mockito:mockito-core:4.1.0") 25 | 26 | testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:5.8.1") 27 | } 28 | 29 | tasks.named("test") { 30 | useJUnitPlatform() 31 | } -------------------------------------------------------------------------------- /Ch15/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch15/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch15/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-6.5-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch15/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "Ch15" 2 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/BiFunction.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.Objects; 4 | import java.util.function.Function; 5 | 6 | @FunctionalInterface 7 | public interface BiFunction { 8 | 9 | R apply(T t, U u); 10 | 11 | default java.util.function.BiFunction andThen(Function after) { 12 | Objects.requireNonNull(after); 13 | return (T t, U u) -> after.apply(apply(t, u)); 14 | } 15 | 16 | default Function curry1(T t) { 17 | return u -> this.apply(t, u); 18 | } 19 | 20 | default Function curry2(U u) { 21 | return t -> this.apply(t, u); 22 | } 23 | 24 | } -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/Builder.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | public interface Builder { 4 | T build(); 5 | } 6 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/ClosureExamples.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.concurrent.atomic.AtomicInteger; 4 | import java.util.function.Function; 5 | 6 | public class ClosureExamples { 7 | 8 | public static void main(String[] args) { 9 | ClosureExamples c = new ClosureExamples(); 10 | c.run(); 11 | c.run2(); 12 | } 13 | 14 | void run() { 15 | int i = 42; 16 | Function f = s -> s + i; 17 | // i = 37; 18 | System.out.println(f.apply("Hello ")); 19 | } 20 | 21 | void run2() { 22 | var i = new AtomicInteger(42); 23 | Function f = s -> s + i.get(); 24 | i.set(37); 25 | // i = new AtomicInteger(42); 26 | System.out.println(f.apply("Hello ")); 27 | } 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/DaySupplier.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.time.LocalDate; 4 | import java.util.function.Supplier; 5 | 6 | public class DaySupplier implements Supplier { 7 | private LocalDate current = LocalDate.now().plusDays(1); 8 | 9 | @Override 10 | public LocalDate get() { 11 | var tmp = current; 12 | current = current.plusDays(1); 13 | return tmp; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/DepositMain.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.lang.reflect.Field; 4 | import java.time.LocalDate; 5 | 6 | public class DepositMain { 7 | public static void main(String[] args) { 8 | var account = new Account(100); 9 | var deposit = Deposit.of(42.0, LocalDate.now(), account); 10 | try { 11 | Field f = Deposit.class.getDeclaredField("amount"); 12 | f.setAccessible(true); 13 | f.setDouble(deposit, 21.0); 14 | System.out.println("Value: "+ deposit.amount()); 15 | } catch (NoSuchFieldException e) { 16 | e.printStackTrace(); 17 | } catch (IllegalAccessException e) { 18 | e.printStackTrace(); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/Main.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | public class Main { 4 | public static void main(String[] args) { 5 | var cl = Main.class.getClassLoader(); 6 | System.out.println(cl); 7 | System.out.println(cl.getParent()); 8 | System.out.println(cl.getParent().getParent()); 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/Prefixer.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.function.Function; 4 | 5 | public class Prefixer implements Function> { 6 | @Override 7 | public Function apply(String prefix) { 8 | return s -> prefix +": "+ s; 9 | } 10 | } 11 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/PrefixerMain.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.function.Function; 4 | 5 | public class PrefixerMain { 6 | public static void main(String[] args) { 7 | // var prefixer = new Prefixer(); 8 | 9 | Function> prefixer = prefix -> { 10 | return s -> prefix +": "+ s; 11 | }; 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/PrefixerOld.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.function.Function; 4 | 5 | public class PrefixerOld implements Function> { 6 | @Override 7 | public Function apply(String prefix) { 8 | return new Function() { 9 | @Override 10 | public String apply(String s) { 11 | return prefix +": "+ s; 12 | } 13 | }; 14 | } 15 | } 16 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/StreamExamples.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.stream.Stream; 4 | 5 | public class StreamExamples { 6 | public static void main(String[] args) { 7 | final var tomorrow = new DaySupplier(); 8 | Stream.generate(() -> tomorrow.get()) 9 | .limit(10) 10 | .forEach(System.out::println); 11 | } 12 | } 13 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/StringApplier.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | import java.util.function.Function; 4 | 5 | public interface StringApplier { 6 | String apply(String input, Function f); 7 | } 8 | -------------------------------------------------------------------------------- /Ch15/src/main/java/ch15/TailRecNaive.java: -------------------------------------------------------------------------------- 1 | package ch15; 2 | 3 | public class TailRecNaive { 4 | public static void main(String[] args) { 5 | long l = Long.parseLong(args[0]); 6 | System.out.println(tailrecFactorial(l)); 7 | } 8 | 9 | private static long helpFact(long i, long j) { 10 | if (i == 0) { 11 | return j; 12 | } 13 | return helpFact(i - 1, i * j); 14 | } 15 | 16 | public static long tailrecFactorial(long n) { 17 | if (n <= 0) { 18 | return 1; 19 | } 20 | return helpFact(n, 1); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Ch16/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 16 - Advanced concurrent programming 2 | 3 | ## `Ch16/src/ch16` 4 | 5 | This directory contains examples of various concurrent programming concepts. 6 | 7 | You can load at the `Ch16` directory in an IDE to run these examples. Java 11+ 8 | is required. 9 | 10 | * `src/ch16/SorterMain.java` - Demonstrating parallel sorting on the `ForkJoinPool` 11 | * `src/ch16/CFExamples.java` - CompletableFuture examples 12 | 13 | Alternatively, the samples may be compiled and run at the command-line as 14 | follows: 15 | 16 | ``` 17 | cd Ch16/src/ch16 18 | javac *.java 19 | java -cp .. ch16.SorterMain 20 | ``` 21 | 22 | ## `Ch16/src/ch16/clj` 23 | 24 | This directory contains examples involving Clojure persistent types. 25 | 26 | * `src/ch16/clj/PersistentExamples.java` - Examples using Clojure persistent types from Java 27 | * `src/ch16/clj/PersistentVector.java` - Example implementation of Clojure `PersistentVector` 28 | 29 | ## `Ch16/coroutine-app` 30 | 31 | This directory contains an example Kotlin application walking through some of 32 | the basics of coroutine launching. 33 | 34 | It may be loaded either with the entire `Ch16` directory, or directly itself in 35 | an IDE. It requires Kotlin be installed. 36 | 37 | See `Ch16/coroutines-app/coroutines-app.bytecode` for the decompiled bytecode 38 | of a coroutine app. 39 | -------------------------------------------------------------------------------- /Ch16/coroutines-app/build.gradle.kts: -------------------------------------------------------------------------------- 1 | group = "com.wellgrounded" 2 | version = "0.1.0" 3 | 4 | plugins { 5 | kotlin("jvm") version "1.6.10" 6 | application 7 | } 8 | 9 | application { 10 | mainClass.set("com.wellgrounded.kotlin.MainKt") 11 | } 12 | 13 | tasks.jar { 14 | manifest { 15 | attributes("Main-Class" to application.mainClass) 16 | } 17 | } 18 | 19 | repositories { 20 | mavenCentral() 21 | mavenLocal() 22 | } 23 | 24 | dependencies { 25 | implementation(kotlin("stdlib")) 26 | implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.6.0") 27 | } 28 | -------------------------------------------------------------------------------- /Ch16/coroutines-app/dump: -------------------------------------------------------------------------------- 1 | find build -name "*.class" -exec javap -c {} \; > coroutines-app.bytecode 2 | -------------------------------------------------------------------------------- /Ch16/coroutines-app/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch16/coroutines-app/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch16/coroutines-app/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.3.3-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch16/coroutines-app/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | rootProject.name = "coroutines-app" 2 | -------------------------------------------------------------------------------- /Ch16/src/ch16/NumberService.java: -------------------------------------------------------------------------------- 1 | package ch16; 2 | 3 | import java.util.concurrent.*; 4 | import java.util.stream.*; 5 | 6 | public class NumberService { 7 | public static long findPrime(int n) { 8 | try { 9 | Thread.sleep(5_000); 10 | } catch (InterruptedException e) { 11 | throw new CancellationException("interrupted"); 12 | } 13 | return 42L; 14 | } 15 | 16 | } 17 | -------------------------------------------------------------------------------- /Ch16/src/ch16/SorterMain.java: -------------------------------------------------------------------------------- 1 | package ch16; 2 | 3 | import java.util.ArrayList; 4 | import java.util.Collections; 5 | import java.util.concurrent.ForkJoinPool; 6 | 7 | public class SorterMain { 8 | public static void main(String[] args) { 9 | var main = new SorterMain(); 10 | try { 11 | main.run(); 12 | } catch (InterruptedException e) { 13 | e.printStackTrace(); 14 | } 15 | } 16 | 17 | void run() throws InterruptedException { 18 | var transactions = new ArrayList(); 19 | var accs = new Account[] { 20 | new Account(1000), 21 | new Account(1000)}; 22 | for (var i = 0; i < 256; i = i + 1) { 23 | transactions.add(Transaction.of(accs[i % 2], accs[(i + 1) % 2], 1)); 24 | Thread.sleep(1); 25 | } 26 | Collections.shuffle(transactions); 27 | 28 | var sorter = new TransactionSorter(transactions); 29 | var pool = new ForkJoinPool(4); 30 | 31 | pool.invoke(sorter); 32 | 33 | for (var txn : sorter.getResult()) { 34 | System.out.println(txn); 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /Ch17/README.md: -------------------------------------------------------------------------------- 1 | # Chapter 17 - Modern internals 2 | 3 | ## `Ch17/ch17` 4 | 5 | This directory contains multiple examples. It may be loaded in IDE at the 6 | `Ch17` level. Java 17+ is required. 7 | 8 | * `ch17/Concat.java` - Basic string concatenation 9 | * `ch17/HiddenExamples.java` - Creating a hidden class 10 | * `ch17/MHExamples.java` - Method handler retrieval example 11 | * `ch17/ReflectionExamples.java` - Reflection example 12 | 13 | Alternatively, the samples may be compiled and run at the command-line as 14 | follows: 15 | 16 | ``` 17 | cd Ch17/ch17 18 | javac *.java 19 | java -cp .. ch17.ReflectionExamples 20 | ``` 21 | 22 | -------------------------------------------------------------------------------- /Ch17/ch17/Account.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | public interface Account { 4 | boolean withdraw(int amount); 5 | 6 | void deposit(int amount); 7 | 8 | int getBalance(); 9 | 10 | boolean transferTo(Account other, int amount); 11 | } 12 | -------------------------------------------------------------------------------- /Ch17/ch17/Concat.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | import java.lang.invoke.MutableCallSite; 4 | 5 | public class Concat { 6 | public static void main(String[] args) { 7 | String str = "foo"; 8 | if (args.length > 0) { 9 | str = args[0]; 10 | } 11 | System.out.println("this is my string: " + str); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ch17/ch17/ExamplePrivate.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | import java.lang.invoke.MethodHandle; 4 | import java.lang.invoke.MethodHandles; 5 | import java.lang.invoke.MethodType; 6 | import java.lang.reflect.Method; 7 | 8 | public class ExamplePrivate { 9 | public void entry() { 10 | callThePrivate(); 11 | } 12 | 13 | private void callThePrivate() { 14 | System.out.println("Private method"); 15 | } 16 | 17 | public Method makeReflective() { 18 | Method meth = null; 19 | 20 | try { 21 | Class[] argTypes = new Class[] { Void.class }; 22 | meth = ExamplePrivate.class.getDeclaredMethod("cancel", argTypes); 23 | meth.setAccessible(true); 24 | } catch (IllegalArgumentException | NoSuchMethodException | SecurityException e) { 25 | throw (AssertionError)new AssertionError().initCause(e); 26 | } 27 | 28 | return meth; 29 | } 30 | 31 | 32 | public class Proxy { 33 | private Proxy() {} 34 | 35 | public static void invoke(ExamplePrivate priv) { 36 | priv.callThePrivate(); 37 | } 38 | } 39 | 40 | public MethodHandle makeMh() { 41 | MethodHandle mh; 42 | var desc = MethodType.methodType(void.class); 43 | 44 | try { 45 | mh = MethodHandles.lookup().findVirtual(ExamplePrivate.class, "callThePrivate", desc); 46 | } catch (NoSuchMethodException | IllegalAccessException e) { 47 | throw (AssertionError)new AssertionError().initCause(e); 48 | } 49 | 50 | return mh; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Ch17/ch17/HiddenExamples.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | import java.io.IOException; 4 | import java.lang.invoke.MethodHandles; 5 | import java.nio.file.Files; 6 | import java.nio.file.Path; 7 | 8 | public class HiddenExamples { 9 | public static void main(String[] args) { 10 | var m = new HiddenExamples(); 11 | try { 12 | m.run(); 13 | } catch (IOException | IllegalAccessException e) { 14 | e.printStackTrace(); 15 | } 16 | } 17 | 18 | private void run() throws IOException, IllegalAccessException { 19 | var fName = "/Users/ben/projects/books/resources/Ch15/ch15/Concat.class"; 20 | var buffy = Files.readAllBytes(Path.of(fName)); 21 | var lookup = MethodHandles.lookup(); 22 | var hiddenLookup = lookup.defineHiddenClass(buffy, true); 23 | var klazz = hiddenLookup.lookupClass(); 24 | System.out.println(klazz.getName()); 25 | } 26 | } 27 | -------------------------------------------------------------------------------- /Ch17/ch17/MHExamples.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | import java.lang.invoke.MethodType; 4 | 5 | public class MHExamples { 6 | public static void main(String[] args) { 7 | var mt = MethodType.methodType(String.class); 8 | } 9 | 10 | 11 | 12 | } 13 | -------------------------------------------------------------------------------- /Ch17/ch17/ReflectionExamples.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | import java.lang.reflect.InvocationTargetException; 4 | import java.lang.reflect.Method; 5 | 6 | public class ReflectionExamples { 7 | 8 | public static void main(String[] args) { 9 | var m = new ReflectionExamples(); 10 | try { 11 | m.run(); 12 | } catch (NoSuchMethodException | InvocationTargetException | IllegalAccessException e) { 13 | e.printStackTrace(); 14 | } 15 | } 16 | 17 | private void run() throws NoSuchMethodException, InvocationTargetException, IllegalAccessException { 18 | Class clazz = ReflectionExamples.class; 19 | Method m = clazz.getMethod("toString"); 20 | Object ret = m.invoke(this); 21 | System.out.println(ret); 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /Ch17/ch17/SynchronizedAccount.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | public class SynchronizedAccount implements ch17.Account { 4 | private int balance; 5 | 6 | public SynchronizedAccount(int openingBalance) { 7 | balance = openingBalance; 8 | } 9 | 10 | @Override 11 | public int getBalance() { 12 | synchronized (this) { 13 | return balance; 14 | } 15 | } 16 | 17 | @Override 18 | public void deposit(int amount) { 19 | // Check to see amount > 0, throw if not 20 | synchronized (this) { 21 | balance = balance + amount; 22 | } 23 | } 24 | 25 | @Override 26 | public boolean withdraw(int amount) { 27 | // Check to see amount > 0, throw if not 28 | synchronized (this) { 29 | if (balance >= amount) { 30 | balance = balance - amount; 31 | return true; 32 | } 33 | } 34 | return false; 35 | } 36 | 37 | @Override 38 | public boolean transferTo(final Account other, final int amount) { 39 | // Check to see amount > 0, throw if not 40 | synchronized (this) { 41 | if (balance >= amount) { 42 | balance = balance - amount; 43 | synchronized (other) { 44 | other.deposit(amount); 45 | } 46 | return true; 47 | } 48 | } 49 | return false; 50 | } 51 | } -------------------------------------------------------------------------------- /Ch17/ch17/VHAccount.java: -------------------------------------------------------------------------------- 1 | package ch17; 2 | 3 | import java.lang.invoke.MethodHandles; 4 | import java.lang.invoke.VarHandle; 5 | 6 | public class VHAccount implements ch17.Account { 7 | private static final VarHandle vh; 8 | private volatile int balance = 0; 9 | 10 | static { 11 | try { 12 | MethodHandles.Lookup l = MethodHandles.lookup(); 13 | vh = l.findVarHandle(VHAccount.class, "balance", int.class); 14 | } catch (Exception ex) { throw new Error(ex); } 15 | } 16 | 17 | @Override 18 | public void deposit(int amount) { 19 | // Check to see amount > 0, throw if not 20 | vh.getAndAdd(this, amount); 21 | } 22 | 23 | public VHAccount(int openingBalance) { 24 | balance = openingBalance; 25 | } 26 | 27 | @Override 28 | public int getBalance() { 29 | return balance; 30 | } 31 | 32 | @Override 33 | public boolean withdraw(int amount) { 34 | // Check to see amount > 0, throw if not 35 | var currBal = balance; 36 | var newBal = currBal - amount; 37 | if (newBal >= 0) { 38 | return vh.compareAndSet(this, currBal, newBal); 39 | } 40 | return false; 41 | } 42 | 43 | @Override 44 | public boolean transferTo(Account other, int amount) { 45 | // Check to see amount > 0, throw if not 46 | if (withdraw(amount)) { 47 | other.deposit(amount); 48 | return true; 49 | } 50 | return false; 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /Ch18/Panama/.gitattributes: -------------------------------------------------------------------------------- 1 | # 2 | # https://help.github.com/articles/dealing-with-line-endings/ 3 | # 4 | # These are explicitly windows files and should use crlf 5 | *.bat text eol=crlf 6 | 7 | -------------------------------------------------------------------------------- /Ch18/Panama/.gitignore: -------------------------------------------------------------------------------- 1 | # Ignore Gradle project-specific cache directory 2 | .gradle 3 | 4 | # Ignore Gradle build output directory 5 | build 6 | 7 | # Don't check in libspng clone 8 | libspng 9 | 10 | # Don't check in wrappers either 11 | src/main/java/org/* 12 | -------------------------------------------------------------------------------- /Ch18/Panama/build.gradle.kts: -------------------------------------------------------------------------------- 1 | plugins { 2 | id("org.beryx.jlink") version("2.24.2") 3 | } 4 | 5 | repositories { 6 | mavenCentral() 7 | } 8 | 9 | application { 10 | mainModule.set("wgjd.png") 11 | mainClass.set("wgjd.png.PngReader") 12 | } 13 | 14 | java { 15 | modularity.inferModulePath.set(true) 16 | } 17 | 18 | sourceSets { 19 | main { 20 | java { 21 | setSrcDirs(listOf("src/main/java/org", 22 | "src/main/java/wgjd.png")) 23 | } 24 | } 25 | } 26 | 27 | tasks.withType { 28 | options.compilerArgs = listOf() 29 | } 30 | 31 | tasks.jar { 32 | manifest { 33 | attributes("Main-Class" to application.mainClassName) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /Ch18/Panama/gradle/wrapper/gradle-wrapper.jar: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/well-grounded-java/resources/d70920d2f31946ea8ce12bad5f957667a9b9c96e/Ch18/Panama/gradle/wrapper/gradle-wrapper.jar -------------------------------------------------------------------------------- /Ch18/Panama/gradle/wrapper/gradle-wrapper.properties: -------------------------------------------------------------------------------- 1 | distributionBase=GRADLE_USER_HOME 2 | distributionPath=wrapper/dists 3 | distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-bin.zip 4 | zipStoreBase=GRADLE_USER_HOME 5 | zipStorePath=wrapper/dists 6 | -------------------------------------------------------------------------------- /Ch18/Panama/settings.gradle.kts: -------------------------------------------------------------------------------- 1 | /* 2 | * This file was generated by the Gradle 'init' task. 3 | * 4 | * The settings file is used to specify which projects to include in your build. 5 | * 6 | * Detailed information about configuring a multi-project build in Gradle can be found 7 | * in the user manual at https://docs.gradle.org/7.4.2/userguide/multi_project_builds.html 8 | * This project uses @Incubating APIs which are subject to change. 9 | */ 10 | 11 | rootProject.name = "Panama" 12 | -------------------------------------------------------------------------------- /Ch18/Panama/src/main/java/wgjd.png/module-info.java: -------------------------------------------------------------------------------- 1 | module wgjd.png { 2 | exports wgjd.png; 3 | 4 | requires jdk.incubator.foreign; 5 | } 6 | -------------------------------------------------------------------------------- /Ch18/ch18/AmberExamples.java: -------------------------------------------------------------------------------- 1 | package ch18; 2 | 3 | import static ch17.fx.CurrencyPair.*; 4 | import static ch17.fx.Side.*; 5 | import ch18.fx.sealed.FXOrder; 6 | import ch18.fx.sealed.LimitOrder; 7 | 8 | import java.time.LocalDateTime; 9 | 10 | public class AmberExamples { 11 | 12 | public static void main(String[] args) { 13 | 14 | } 15 | 16 | void run2(Object o) { 17 | if (o instanceof String[] { String s1, String s2, ... }){ 18 | System.out.println(s1 + s2); 19 | } 20 | } 21 | 22 | void run1() { 23 | FXOrder order = new LimitOrder(1, GBPUSD, BUY, LocalDateTime.now(), 1.25,1000); 24 | 25 | var isMarket = switch (order) { 26 | case MarketOrder(int units, CurrencyPair pair, Side side, LocalDateTime sent, boolean allOrNothing) -> true; 27 | case LimitOrder(int units, CurrencyPair pair, Side side, LocalDateTime sent double price, int ttl) -> false; 28 | }; 29 | System.out.println(isMarket); 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /Ch18/ch18/SwitchExamples.java: -------------------------------------------------------------------------------- 1 | package ch18; 2 | 3 | import java.time.DayOfWeek; 4 | 5 | public class SwitchExamples { 6 | public static boolean isWorkDay(DayOfWeek day){ 7 | var today = switch(day) { 8 | case SATURDAY, SUNDAY -> false; 9 | case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY -> true; 10 | }; 11 | 12 | // Do any further processing to e.g. account for public holidays... 13 | 14 | return today; 15 | } 16 | 17 | public static boolean isWorkDay2(DayOfWeek day){ 18 | var today = switch(day) { 19 | case SATURDAY, SUNDAY: yield false; 20 | case MONDAY, TUESDAY, WEDNESDAY, THURSDAY, FRIDAY: yield true; 21 | }; 22 | 23 | // Do any further processing to e.g. account for public holidays... 24 | 25 | return today; 26 | } 27 | 28 | 29 | } 30 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/CurrencyPair.java: -------------------------------------------------------------------------------- 1 | package ch18.fx; 2 | 3 | public enum CurrencyPair { 4 | GBPUSD, GBPEUR, USDEUR 5 | } 6 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/OrderPartition.java: -------------------------------------------------------------------------------- 1 | package ch18.fx; 2 | 3 | import ch18.fx.records.FXOrder; 4 | 5 | record OrderPartition(CurrencyPair pair, Side side) { 6 | public OrderPartition(FXOrder order) { 7 | this(order.pair(), order.side()); 8 | } 9 | 10 | } -------------------------------------------------------------------------------- /Ch18/ch18/fx/OrderType.java: -------------------------------------------------------------------------------- 1 | package ch18.fx; 2 | 3 | enum OrderType { 4 | MARKET, 5 | LIMIT 6 | } 7 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/Side.java: -------------------------------------------------------------------------------- 1 | package ch18.fx; 2 | 3 | public enum Side { 4 | BUY, SELL 5 | } 6 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/records/FXOrder.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.records; 2 | 3 | import ch18.fx.CurrencyPair; 4 | import ch18.fx.Side; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | public record FXOrder(int units, 9 | CurrencyPair pair, 10 | Side side, 11 | double price, 12 | LocalDateTime sentAt, 13 | int ttl) { 14 | 15 | public FXOrder { 16 | if (units < 1) { 17 | throw new IllegalArgumentException("FXOrder units must be positive"); 18 | } 19 | if (ttl < 0) { 20 | throw new IllegalArgumentException("FXOrder TTL must be positive, or 0 for market orders"); 21 | } 22 | if (price <= 0.0) { 23 | throw new IllegalArgumentException("FXOrder price must be positive"); 24 | } 25 | } 26 | 27 | public static FXOrder of(CurrencyPair pair, Side side, double price) { 28 | return new FXOrder(1, pair, side, price, LocalDateTime.now(), 1000); 29 | } 30 | 31 | } 32 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/FXAccepted.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record FXAccepted(LocalDateTime timestamp, long orderId) 6 | implements FXOrderResponse { 7 | public static FXAccepted of(long orderId) { 8 | if (orderId <= 0) { 9 | throw new IllegalArgumentException("Order ID must be > 0"); 10 | } 11 | return new FXAccepted(LocalDateTime.now(), orderId); 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/FXCancelled.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record FXCancelled(LocalDateTime timestamp, long orderId, long unitsCancelled) 6 | implements FXOrderResponse { 7 | 8 | public FXCancelled { 9 | if (orderId < 0) { 10 | throw new IllegalArgumentException("FXCancelled orderId must be positive"); 11 | } 12 | } 13 | 14 | public static FXCancelled of(long orderId, long unitsCancelled) { 15 | return new FXCancelled(LocalDateTime.now(), orderId, unitsCancelled); 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/FXFill.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record FXFill(LocalDateTime timestamp, long orderId, double price, long units) 6 | implements FXOrderResponse { 7 | 8 | public FXFill { 9 | if (units < 1) { 10 | throw new IllegalArgumentException("FXFill units must be positive"); 11 | } 12 | if (orderId < 0) { 13 | throw new IllegalArgumentException("FXFill orderId must be positive"); 14 | } 15 | if (price <= 0.0) { 16 | throw new IllegalArgumentException("FXFill price must be positive"); 17 | } 18 | } 19 | 20 | public static FXFill of(long orderId, double price, long units) { 21 | return new FXFill(LocalDateTime.now(), orderId, price, units); 22 | } 23 | 24 | } 25 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/FXOrder.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import ch17.fx.CurrencyPair; 4 | import ch17.fx.Side; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | public sealed interface FXOrder permits MarketOrder, LimitOrder { 9 | int units(); 10 | CurrencyPair pair(); 11 | Side side(); 12 | LocalDateTime sentAt(); 13 | } 14 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/FXOrderResponse.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public sealed interface FXOrderResponse 6 | permits FXAccepted, FXFill, FXReject, FXCancelled { 7 | LocalDateTime timestamp(); 8 | long orderId(); 9 | } 10 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/FXReject.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import java.time.LocalDateTime; 4 | 5 | public record FXReject(LocalDateTime timestamp, long orderId, String reason) 6 | implements FXOrderResponse { 7 | 8 | public FXReject { 9 | if (orderId < 0) { 10 | throw new IllegalArgumentException("FXCancelled orderId must be positive"); 11 | } 12 | } 13 | 14 | public static FXReject of(long orderId, String reason) { 15 | return new FXReject(LocalDateTime.now(), orderId, reason); 16 | } 17 | 18 | 19 | } 20 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/LimitOrder.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import ch17.fx.CurrencyPair; 4 | import ch17.fx.Side; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | public record LimitOrder(int units, 9 | CurrencyPair pair, 10 | Side side, 11 | LocalDateTime sentAt, 12 | double price, 13 | int ttl) implements FXOrder { 14 | // constructors and factories ommitted 15 | } 16 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/MarketOrder.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | import ch17.fx.CurrencyPair; 4 | import ch17.fx.Side; 5 | 6 | import java.time.LocalDateTime; 7 | 8 | public record MarketOrder(int units, 9 | CurrencyPair pair, 10 | Side side, 11 | LocalDateTime sentAt, 12 | boolean allOrNothing) implements FXOrder { 13 | // constructors and factories ommitted 14 | } 15 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/OrderPatterns.java: -------------------------------------------------------------------------------- 1 | package ch18.fx.sealed; 2 | 3 | public class OrderPatterns { 4 | 5 | public static void main(String[] args) { 6 | 7 | } 8 | 9 | void run() { 10 | FXOrderResponse resp = FXAccepted.of(1137); 11 | var msg = switch (resp) { 12 | case FXAccepted a -> a.orderId() + " Accepted"; 13 | case FXFill f && f.units() < 100 -> f.orderId() + " Small Fill"; 14 | case FXFill f -> f.orderId() + " Filled "+ f.units(); 15 | case FXReject r -> r.orderId() + " Rejected: "+ r.reason(); 16 | case FXCancelled c -> c.orderId() + " Cancelled"; 17 | case null -> "Order is null"; 18 | }; 19 | System.out.println(msg); 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Ch18/ch18/fx/sealed/Pet.java: -------------------------------------------------------------------------------- 1 | package ch16.fx.sealed; 2 | 3 | public abstract sealed class Pet { 4 | private final String name; 5 | 6 | protected Pet(String name) { 7 | this.name = name; 8 | } 9 | 10 | public String name() { 11 | return name; 12 | } 13 | 14 | public static final class Cat extends Pet { 15 | public Cat(String name) { 16 | super(name); 17 | } 18 | 19 | void meow() { 20 | System.out.println(name() +" meows"); 21 | } 22 | } 23 | 24 | public static final class Dog extends Pet { 25 | public Dog(String name) { 26 | super(name); 27 | } 28 | 29 | void bark() { 30 | System.out.println(name() +" barks"); 31 | } 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /Misc/misc/FindJavaVisitor.java: -------------------------------------------------------------------------------- 1 | package misc; 2 | 3 | import java.nio.file.FileVisitResult; 4 | import java.nio.file.Path; 5 | import java.nio.file.SimpleFileVisitor; 6 | import java.nio.file.attribute.BasicFileAttributes; 7 | 8 | public class FindJavaVisitor extends SimpleFileVisitor { 9 | @Override 10 | public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) { 11 | if (file != null && attrs != null) { 12 | if (file.getFileName().toString().endsWith(".java")) { 13 | System.out.println(file.getFileName()); 14 | } 15 | } 16 | return FileVisitResult.CONTINUE; 17 | } 18 | } 19 | --------------------------------------------------------------------------------